Skip to content

Instantly share code, notes, and snippets.

@v4lerio
Forked from askasim/ThemeSwitcher.vue
Last active November 7, 2023 22:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save v4lerio/4815aa3e84e23b5ade1744f003a10176 to your computer and use it in GitHub Desktop.
Save v4lerio/4815aa3e84e23b5ade1744f003a10176 to your computer and use it in GitHub Desktop.
Laravel Jetstream Dark Mode Switch
<template>
<div class="flex justify-center">
<div class="relative">
<button
class="flex text-md border-2 border-transparent rounded-full focus:outline-none focus:border-gray-300 transition"
@click="toggleDropdown"
>
<SunIcon
v-if="option === 'light'"
class="h-6 w-6"
aria-hidden="true"
/>
<MoonIcon
v-if="option === 'dark'"
class="h-6 w-6"
aria-hidden="true"
/>
<ComputerDesktopIcon
v-if="option === 'system'"
class="h-6 w-6"
aria-hidden="true"
/>
</button>
<div
v-if="isDropdownOpen"
class="absolute right-0 mt-2 bg-white border border-gray-300 rounded-b shadow-lg dark:bg-gray-800 dark:border-gray-700 rounded"
>
<button
@click="setOption('light')"
class="darkflex themeColor-center hover:bg-gray-100 py-1 hover:bg-gray-50 dark:hover:bg-gray-800 block w-full text-left cursor-pointer py-2 px-3 focus:outline-none focus:ring rounded truncate whitespace-nowrap text-gray-500 active:text-gray-600 dark:text-gray-500 dark:hover:text-gray-400 dark:active:text-gray-600 flex themeColor-center hover:bg-gray-100 py-1"
>
<SunIcon class="h-5 w-5" aria-hidden="true"/>
<span class="ml-2">Light</span>
</button>
<button
@click="setOption('dark')"
class="flex themeColor-center hover:bg-gray-100 py-1 hover:bg-gray-50 dark:hover:bg-gray-800 block w-full text-left cursor-pointer py-2 px-3 focus:outline-none focus:ring rounded truncate whitespace-nowrap text-gray-500 active:text-gray-600 dark:text-gray-500 dark:hover:text-gray-400 dark:active:text-gray-600 flex themeColor-center hover:bg-gray-100 py-1"
>
<MoonIcon class="h-5 w-5" aria-hidden="true"/>
<span class="ml-2">Dark</span>
</button>
<button
@click="setOption('system')"
class="flex themeColor-center hover:bg-gray-100 py-1 hover:bg-gray-50 dark:hover:bg-gray-800 block w-full text-left cursor-pointer py-2 px-3 focus:outline-none focus:ring rounded truncate whitespace-nowrap text-gray-500 active:text-gray-600 dark:text-gray-500 dark:hover:text-gray-400 dark:active:text-gray-600 flex themeColor-center hover:bg-gray-100 py-1"
>
<ComputerDesktopIcon class="h-5 w-5" aria-hidden="true"/>
<span class="ml-2">System</span>
</button>
</div>
</div>
</div>
</template>
<script setup>
import {ref, onMounted, watch} from 'vue';
import {SunIcon, MoonIcon, ComputerDesktopIcon} from '@heroicons/vue/20/solid';
const systemDarkMode = window.matchMedia('(prefers-color-scheme: dark)');
const option = ref(localStorage.getItem('option'));
const isDropdownOpen = ref(false);
const toggleDropdown = () => {
isDropdownOpen.value = !isDropdownOpen.value;
};
const setOption = (selectedOption) => {
localStorage.setItem('option', selectedOption);
option.value = selectedOption
isDropdownOpen.value = false;
}
const setTheme = () => {
if (option.value === 'system') {
window.matchMedia('(prefers-color-scheme: dark)').matches ? toggleDarkClass('dark') : toggleDarkClass('light')
} else {
option.value === 'dark' ? toggleDarkClass('dark') : toggleDarkClass('light')
}
};
function switchToLight() {
// Select all elements with a class attribute
document.querySelectorAll('[class]').forEach(el => {
if (el instanceof SVGElement) {
return
}
// Loop through each class in the classList
let updatedClasses = [];
el.classList.forEach(cls => {
if (cls.startsWith('dark:')) {
let prefix = 'dark:'
updatedClasses.push('none:' + cls.slice(prefix.length));
} else {
updatedClasses.push(cls)
}
});
// Replace the entire className with the new one
el.className = updatedClasses.join(' ');
});
}
function switchToDark() {
// Select all elements with a class attribute
document.querySelectorAll('[class]').forEach(el => {
if (el instanceof SVGElement) {
return
}
// Loop through each class in the classList
let updatedClasses = [];
el.classList.forEach(cls => {
if (cls.startsWith('none:')) {
let prefix = 'none:'
updatedClasses.push('dark:' + cls.slice(prefix.length));
} else {
updatedClasses.push(cls)
}
});
// Replace the entire className with the new one
el.className = updatedClasses.join(' ');
});
}
function togglePrefixFromAllClassNames() {
let prefix
// Select all elements with a class attribute
document.querySelectorAll('[class]').forEach(el => {
if (el instanceof SVGElement) {
return
}
// Loop through each class in the classList
let updatedClasses = [];
el.classList.forEach(cls => {
if (cls.startsWith('dark:')) {
prefix = 'dark:'
updatedClasses.push('none:' + cls.slice(prefix.length));
} else if (cls.startsWith('none:')) {
prefix = 'none:'
updatedClasses.push('dark:' + cls.slice(prefix.length));
} else {
updatedClasses.push(cls)
}
});
// Replace the entire className with the new one
el.className = updatedClasses.join(' ');
});
}
const toggleDarkClass = (className) => {
if (className === 'dark') {
switchToDark('none:');
} else {
switchToLight('dark:')
}
};
watch(option, setTheme);
onMounted(() => {
if (!option.value) {
setOption('system')
}
setTheme();
systemDarkMode.addListener((event) => {
if (option.value === 'system') {
if (event.matches) {
toggleDarkClass('dark')
} else {
toggleDarkClass('light')
}
}
});
});
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment