Add setting to toggle light/dark mode
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
parent
610570c795
commit
e420713a6f
|
@ -54,6 +54,7 @@ import {
|
|||
defineAsyncComponent,
|
||||
computed,
|
||||
watch,
|
||||
onBeforeUnmount,
|
||||
} from "vue";
|
||||
import { LocationType } from "@/types/user-location.model";
|
||||
import { useMutation, useQuery } from "@vue/apollo-composable";
|
||||
|
@ -155,6 +156,7 @@ onBeforeMount(async () => {
|
|||
});
|
||||
|
||||
const snackbar = inject<Snackbar>("snackbar");
|
||||
const darkModePreference = window.matchMedia("(prefers-color-scheme: dark)");
|
||||
|
||||
onMounted(() => {
|
||||
online.value = window.navigator.onLine;
|
||||
|
@ -187,6 +189,7 @@ onMounted(() => {
|
|||
},
|
||||
});
|
||||
});
|
||||
darkModePreference.addEventListener("change", changeTheme);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
|
@ -289,6 +292,23 @@ watch(config, async (configWatched: IConfig | undefined) => {
|
|||
});
|
||||
|
||||
const isDemoMode = computed(() => config.value?.demoMode);
|
||||
|
||||
const changeTheme = () => {
|
||||
console.debug("changing theme");
|
||||
if (
|
||||
localStorage.getItem("theme") === "dark" ||
|
||||
(!("theme" in localStorage) &&
|
||||
window.matchMedia("(prefers-color-scheme: dark)").matches)
|
||||
) {
|
||||
document.documentElement.classList.add("dark");
|
||||
} else {
|
||||
document.documentElement.classList.remove("dark");
|
||||
}
|
||||
};
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
darkModePreference.removeEventListener("change", changeTheme);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
|
|
@ -194,6 +194,10 @@ body {
|
|||
@apply pl-2;
|
||||
}
|
||||
|
||||
.o-field--addons .o-radio:not(:only-child) input {
|
||||
@apply rounded-full;
|
||||
}
|
||||
|
||||
/* Editor */
|
||||
button.menubar__button {
|
||||
@apply dark:text-white;
|
||||
|
|
|
@ -1408,5 +1408,14 @@
|
|||
"Most recently published": "Most recently published",
|
||||
"Least recently published": "Least recently published",
|
||||
"With the most participants": "With the most participants",
|
||||
"Number of members": "Number of members"
|
||||
"Number of members": "Number of members",
|
||||
"More options": "More options",
|
||||
"Reported by someone anonymously": "Reported by someone anonymously",
|
||||
"Back to homepage": "Back to homepage",
|
||||
"Category list": "Category list",
|
||||
"No categories with public upcoming events on this instance were found.": "No categories with public upcoming events on this instance were found.",
|
||||
"Theme": "Theme",
|
||||
"Adapt to system theme": "Adapt to system theme",
|
||||
"Light": "Light",
|
||||
"Dark": "Dark"
|
||||
}
|
|
@ -1406,5 +1406,14 @@
|
|||
"{timezoneLongName} ({timezoneShortName})": "{timezoneLongName} ({timezoneShortName})",
|
||||
"{title} ({count} todos)": "{title} ({count} todos)",
|
||||
"{username} was invited to {group}": "{username} a été invité à {group}",
|
||||
"© The OpenStreetMap Contributors": "© Les Contributeur⋅ices OpenStreetMap"
|
||||
"© The OpenStreetMap Contributors": "© Les Contributeur⋅ices OpenStreetMap",
|
||||
"More options": "Plus d'options",
|
||||
"Reported by someone anonymously": "Signalé par quelqu'un anonymement",
|
||||
"Back to homepage": "Retour à la page d'accueil",
|
||||
"Category list": "Liste des catégories",
|
||||
"No categories with public upcoming events on this instance were found.": "Aucune catégorie avec des événements publics à venir n'a été trouvée.",
|
||||
"Theme": "Thème",
|
||||
"Adapt to system theme": "S’adapter au thème du système",
|
||||
"Light": "Clair",
|
||||
"Dark": "Sombre"
|
||||
}
|
||||
|
|
|
@ -13,6 +13,36 @@
|
|||
]"
|
||||
/>
|
||||
<div>
|
||||
<o-field :label="t('Theme')" addonsClass="flex flex-col">
|
||||
<o-field>
|
||||
<o-checkbox v-model="systemTheme">{{
|
||||
t("Adapt to system theme")
|
||||
}}</o-checkbox>
|
||||
</o-field>
|
||||
<o-field>
|
||||
<fieldset>
|
||||
<legend class="sr-only">{{ t("Theme") }}</legend>
|
||||
<o-radio
|
||||
:class="{ 'border-mbz-bluegreen border-2': theme === 'light' }"
|
||||
class="p-4 bg-white text-zinc-800 rounded-md mt-2 mr-2"
|
||||
:disabled="systemTheme"
|
||||
v-model="theme"
|
||||
name="theme"
|
||||
native-value="light"
|
||||
>{{ t("Light") }}</o-radio
|
||||
>
|
||||
<o-radio
|
||||
:class="{ 'border-mbz-bluegreen border-2': theme === 'dark' }"
|
||||
class="p-4 bg-zinc-800 rounded-md text-white mt-2 ml-2"
|
||||
:disabled="systemTheme"
|
||||
v-model="theme"
|
||||
name="theme"
|
||||
native-value="dark"
|
||||
>{{ t("Dark") }}</o-radio
|
||||
>
|
||||
</fieldset>
|
||||
</o-field>
|
||||
</o-field>
|
||||
<o-field :label="t('Language')" label-for="setting-language">
|
||||
<o-select
|
||||
:loading="loadingTimezones || loadingUserSettings"
|
||||
|
@ -120,7 +150,7 @@ import { Address, IAddress } from "@/types/address.model";
|
|||
import { useTimezones } from "@/composition/apollo/config";
|
||||
import { useUserSettings } from "@/composition/apollo/user";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { computed, defineAsyncComponent } from "vue";
|
||||
import { computed, defineAsyncComponent, ref, watch } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useMutation } from "@vue/apollo-composable";
|
||||
|
||||
|
@ -140,6 +170,44 @@ useHead({
|
|||
|
||||
// langs: Record<string, string> = langs;
|
||||
|
||||
const theme = ref(localStorage.getItem("theme"));
|
||||
const systemTheme = ref(!("theme" in localStorage));
|
||||
|
||||
watch(systemTheme, (newSystemTheme) => {
|
||||
console.debug("changing system theme", newSystemTheme);
|
||||
if (newSystemTheme) {
|
||||
theme.value = null;
|
||||
localStorage.removeItem("theme");
|
||||
} else {
|
||||
theme.value = "light";
|
||||
localStorage.setItem("theme", theme.value);
|
||||
}
|
||||
changeTheme();
|
||||
});
|
||||
|
||||
watch(theme, (newTheme) => {
|
||||
console.debug("changing theme value", newTheme);
|
||||
if (newTheme) {
|
||||
localStorage.setItem("theme", newTheme);
|
||||
}
|
||||
changeTheme();
|
||||
});
|
||||
|
||||
const changeTheme = () => {
|
||||
console.debug("changing theme to apply");
|
||||
if (
|
||||
localStorage.getItem("theme") === "dark" ||
|
||||
(!("theme" in localStorage) &&
|
||||
window.matchMedia("(prefers-color-scheme: dark)").matches)
|
||||
) {
|
||||
console.debug("applying dark theme");
|
||||
document.documentElement.classList.add("dark");
|
||||
} else {
|
||||
console.debug("removing dark theme");
|
||||
document.documentElement.classList.remove("dark");
|
||||
}
|
||||
};
|
||||
|
||||
const selectedTimezone = computed({
|
||||
get() {
|
||||
if (loggedUser.value?.settings?.timezone) {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
module.exports = {
|
||||
content: ["./public/**/*.html", "./src/**/*.{vue,js,ts,jsx,tsx}"],
|
||||
darkMode: "class",
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
|
|
|
@ -7,6 +7,13 @@
|
|||
<link rel="apple-touch-icon" href="/img/icons/apple-touch-icon-152x152.png" sizes="152x152" />
|
||||
<link rel="mask-icon" href="/img/icons/safari-pinned-tab.svg" color={theme_color()} />
|
||||
<meta name="theme-color" content={theme_color()} />
|
||||
<script>
|
||||
if (localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||
document.documentElement.classList.add('dark')
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark')
|
||||
}
|
||||
</script>
|
||||
<%= if is_root(assigns) do %>
|
||||
<link rel="preload" href="/img/shape-1.svg" as="image" />
|
||||
<link rel="preload" href="/img/shape-2.svg" as="image" />
|
||||
|
|
Loading…
Reference in a new issue