feat: imporve theme toggle button
This commit is contained in:
@@ -67,7 +67,7 @@ const currentPath = pathname.slice(1);
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
id="navbar-collapse-with-animation"
|
id="navbar-collapse-with-animation"
|
||||||
class="hs-collapse hidden grow basis-full overflow-hidden transition-all duration-300 md:block"
|
class="hs-collapse hidden grow basis-full overflow-hidden transition-all duration-300 md:block md:overflow-visible"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="mt-5 flex flex-col gap-x-0 gap-y-4 md:mt-0 md:flex-row md:items-center md:justify-end md:gap-x-4 md:gap-y-0 md:ps-7 lg:gap-x-7"
|
class="mt-5 flex flex-col gap-x-0 gap-y-4 md:mt-0 md:flex-row md:items-center md:justify-end md:gap-x-4 md:gap-y-0 md:ps-7 lg:gap-x-7"
|
||||||
|
|||||||
@@ -5,14 +5,14 @@
|
|||||||
<button
|
<button
|
||||||
id="theme-toggle"
|
id="theme-toggle"
|
||||||
data-theme-toggle
|
data-theme-toggle
|
||||||
class="group dark:hover:bg-steel/30 relative touch-manipulation overflow-hidden rounded-full p-1.5 transition-all duration-300 hover:bg-yellow-300/20 focus:outline-hidden sm:p-2"
|
class="group dark:hover:bg-steel/30 hover:bg-yellow-300/20 transition-all duration-300 relative rounded-full p-1.5 sm:p-2 touch-manipulation"
|
||||||
aria-label="Toggle dark mode"
|
aria-label="Toggle dark mode"
|
||||||
>
|
>
|
||||||
<div class="relative z-10 flex h-5 w-5 items-center justify-center">
|
<div class="relative flex h-5 w-5 items-center justify-center">
|
||||||
<!-- Sun icon -->
|
<!-- Sun icon -->
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
class="icon-light absolute h-5 w-5 scale-100 rotate-0 text-neutral-600 transition-all duration-500 dark:scale-0 dark:-rotate-90 dark:text-neutral-400"
|
class="icon-light absolute h-5 w-5 text-neutral-600 dark:text-neutral-400 scale-100 dark:scale-0 rotate-0 dark:-rotate-90 transition-all duration-500"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
<!-- Moon icon -->
|
<!-- Moon icon -->
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
class="icon-dark absolute h-5 w-5 scale-0 rotate-90 text-neutral-600 transition-all duration-500 dark:scale-100 dark:rotate-0 dark:text-neutral-400"
|
class="icon-dark absolute h-5 w-5 text-neutral-600 dark:text-neutral-400 scale-0 dark:scale-100 rotate-90 dark:rotate-0 transition-all duration-500"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
@@ -43,25 +43,17 @@
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<script is:inline>
|
<script is:inline>
|
||||||
// Use a function to persist theme when using SPA transitions
|
const isDark =
|
||||||
// https://docs.astro.build/en/guides/view-transitions/#script-re-execution
|
localStorage.theme === 'dark' ||
|
||||||
function applyTheme() {
|
(!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
||||||
localStorage.theme === 'dark'
|
document.documentElement.classList.toggle('dark', isDark);
|
||||||
? document.documentElement.classList.add('dark')
|
|
||||||
: document.documentElement.classList.remove('dark');
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener('astro:after-swap', applyTheme);
|
|
||||||
|
|
||||||
applyTheme();
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Use a function to handle theme toggle to ensure it can be called from anywhere
|
|
||||||
function setupThemeToggle() {
|
function setupThemeToggle() {
|
||||||
const themeToggles = document.querySelectorAll('[data-theme-toggle]');
|
const themeToggles = document.querySelectorAll('[data-theme-toggle]');
|
||||||
|
|
||||||
// Create theme switch overlay element if it doesn't exist
|
// Create theme switch overlay element
|
||||||
if (!document.querySelector('.theme-switch-overlay')) {
|
if (!document.querySelector('.theme-switch-overlay')) {
|
||||||
const overlay = document.createElement('div');
|
const overlay = document.createElement('div');
|
||||||
overlay.className = 'theme-switch-overlay fixed inset-0 pointer-events-none z-50';
|
overlay.className = 'theme-switch-overlay fixed inset-0 pointer-events-none z-50';
|
||||||
@@ -70,9 +62,7 @@
|
|||||||
document.body.appendChild(overlay);
|
document.body.appendChild(overlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toggle theme when any theme toggle button is clicked
|
|
||||||
themeToggles.forEach((toggle) => {
|
themeToggles.forEach((toggle) => {
|
||||||
// Add event listeners for both click and touch events
|
|
||||||
['click', 'touchend'].forEach((eventType) => {
|
['click', 'touchend'].forEach((eventType) => {
|
||||||
toggle.addEventListener(
|
toggle.addEventListener(
|
||||||
eventType,
|
eventType,
|
||||||
@@ -92,14 +82,10 @@
|
|||||||
y = e.clientY - rect.top;
|
y = e.clientY - rect.top;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the position variables for the radial gradient
|
|
||||||
document.documentElement.style.setProperty('--x', `${x}px`);
|
document.documentElement.style.setProperty('--x', `${x}px`);
|
||||||
document.documentElement.style.setProperty('--y', `${y}px`);
|
document.documentElement.style.setProperty('--y', `${y}px`);
|
||||||
|
|
||||||
// Get the overlay element
|
|
||||||
const overlay = document.querySelector('.theme-switch-overlay');
|
const overlay = document.querySelector('.theme-switch-overlay');
|
||||||
|
|
||||||
// Determine the new theme
|
|
||||||
const isDark = document.documentElement.classList.contains('dark');
|
const isDark = document.documentElement.classList.contains('dark');
|
||||||
const newTheme = isDark ? 'light' : 'dark';
|
const newTheme = isDark ? 'light' : 'dark';
|
||||||
|
|
||||||
@@ -110,7 +96,6 @@
|
|||||||
overlay.style.opacity = '1';
|
overlay.style.opacity = '1';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add transition class
|
|
||||||
document.documentElement.classList.add('theme-switching');
|
document.documentElement.classList.add('theme-switching');
|
||||||
|
|
||||||
// Force a reflow to ensure all elements update
|
// Force a reflow to ensure all elements update
|
||||||
@@ -124,10 +109,7 @@
|
|||||||
document.documentElement.classList.add('dark');
|
document.documentElement.classList.add('dark');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the preference
|
|
||||||
localStorage.setItem('theme', newTheme);
|
localStorage.setItem('theme', newTheme);
|
||||||
|
|
||||||
// Dispatch a custom event for other components to react to
|
|
||||||
document.dispatchEvent(
|
document.dispatchEvent(
|
||||||
new CustomEvent('themeChanged', {
|
new CustomEvent('themeChanged', {
|
||||||
detail: { isDark: newTheme === 'dark' },
|
detail: { isDark: newTheme === 'dark' },
|
||||||
@@ -137,13 +119,10 @@
|
|||||||
// Force another reflow to ensure all elements update
|
// Force another reflow to ensure all elements update
|
||||||
document.body.offsetHeight;
|
document.body.offsetHeight;
|
||||||
|
|
||||||
// Hide overlay after theme has changed
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (overlay) {
|
if (overlay) {
|
||||||
overlay.style.opacity = '0';
|
overlay.style.opacity = '0';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove transition class after animation completes
|
|
||||||
document.documentElement.classList.remove('theme-switching');
|
document.documentElement.classList.remove('theme-switching');
|
||||||
}, 300);
|
}, 300);
|
||||||
}, 50);
|
}, 50);
|
||||||
@@ -151,25 +130,6 @@
|
|||||||
{ passive: false }
|
{ passive: false }
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add touch feedback
|
|
||||||
toggle.addEventListener(
|
|
||||||
'touchstart',
|
|
||||||
() => {
|
|
||||||
toggle.classList.add('active-touch');
|
|
||||||
},
|
|
||||||
{ passive: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
toggle.addEventListener(
|
|
||||||
'touchend',
|
|
||||||
() => {
|
|
||||||
setTimeout(() => {
|
|
||||||
toggle.classList.remove('active-touch');
|
|
||||||
}, 150);
|
|
||||||
},
|
|
||||||
{ passive: true }
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,61 +161,32 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* Smooth transition for the entire page when theme changes */
|
|
||||||
:global(body) {
|
|
||||||
transition:
|
|
||||||
background-color 0.5s ease,
|
|
||||||
color 0.5s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Theme transition overlay */
|
|
||||||
:global(.theme-switch-overlay) {
|
|
||||||
position: fixed;
|
|
||||||
inset: 0;
|
|
||||||
z-index: 9999;
|
|
||||||
pointer-events: none;
|
|
||||||
transition: opacity 0.3s ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Ensure theme transitions apply to all elements */
|
|
||||||
:global(.theme-switching *) {
|
|
||||||
transition-duration: 0.5s !important;
|
|
||||||
transition-property: background-color, border-color, color, fill, stroke !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Subtle hover animation */
|
/* Subtle hover animation */
|
||||||
#theme-toggle {
|
#theme-toggle {
|
||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
box-shadow: 0 0 0 rgba(0, 0, 0, 0);
|
box-shadow: 0 0 0 rgba(0, 0, 0, 0);
|
||||||
-webkit-tap-highlight-color: transparent; /* Remove default mobile tap highlight */
|
-webkit-tap-highlight-color: transparent;
|
||||||
min-height: 32px; /* Ensure minimum touch target size */
|
min-height: 32px;
|
||||||
min-width: 32px; /* Ensure minimum touch target size */
|
min-width: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Only apply hover effects on non-touch devices */
|
|
||||||
@media (hover: hover) {
|
@media (hover: hover) {
|
||||||
#theme-toggle:hover {
|
#theme-toggle:hover {
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#theme-toggle:hover .icon-light:not(.dark .icon-light) {
|
:global(:root:not(.dark)) #theme-toggle:hover .icon-light {
|
||||||
filter: drop-shadow-sm(0 0 2px rgba(251, 191, 36, 0.6));
|
filter: drop-shadow(0 0 2px rgba(251, 191, 36, 0.6));
|
||||||
transform: scale(1.1) rotate(15deg);
|
transform: scale(1.1) rotate(15deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
#theme-toggle:hover .icon-dark:not(:not(.dark) .icon-dark) {
|
:global(:root.dark) #theme-toggle:hover .icon-dark {
|
||||||
filter: drop-shadow-sm(0 0 2px rgba(129, 140, 248, 0.6));
|
filter: drop-shadow(0 0 2px rgba(129, 140, 248, 0.6));
|
||||||
transform: scale(1.1) rotate(-15deg);
|
transform: scale(1.1) rotate(-15deg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Touch feedback */
|
|
||||||
#theme-toggle.active-touch {
|
|
||||||
transform: scale(0.95);
|
|
||||||
transition: transform 0.15s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Optimize animations for mobile */
|
/* Optimize animations for mobile */
|
||||||
@media (prefers-reduced-motion: reduce) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
.icon-light,
|
.icon-light,
|
||||||
|
|||||||
Reference in New Issue
Block a user