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,
|
defineAsyncComponent,
|
||||||
computed,
|
computed,
|
||||||
watch,
|
watch,
|
||||||
|
onBeforeUnmount,
|
||||||
} from "vue";
|
} from "vue";
|
||||||
import { LocationType } from "@/types/user-location.model";
|
import { LocationType } from "@/types/user-location.model";
|
||||||
import { useMutation, useQuery } from "@vue/apollo-composable";
|
import { useMutation, useQuery } from "@vue/apollo-composable";
|
||||||
|
@ -155,6 +156,7 @@ onBeforeMount(async () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const snackbar = inject<Snackbar>("snackbar");
|
const snackbar = inject<Snackbar>("snackbar");
|
||||||
|
const darkModePreference = window.matchMedia("(prefers-color-scheme: dark)");
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
online.value = window.navigator.onLine;
|
online.value = window.navigator.onLine;
|
||||||
|
@ -187,6 +189,7 @@ onMounted(() => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
darkModePreference.addEventListener("change", changeTheme);
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
|
@ -289,6 +292,23 @@ watch(config, async (configWatched: IConfig | undefined) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const isDemoMode = computed(() => config.value?.demoMode);
|
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>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
|
@ -194,6 +194,10 @@ body {
|
||||||
@apply pl-2;
|
@apply pl-2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.o-field--addons .o-radio:not(:only-child) input {
|
||||||
|
@apply rounded-full;
|
||||||
|
}
|
||||||
|
|
||||||
/* Editor */
|
/* Editor */
|
||||||
button.menubar__button {
|
button.menubar__button {
|
||||||
@apply dark:text-white;
|
@apply dark:text-white;
|
||||||
|
|
|
@ -1408,5 +1408,14 @@
|
||||||
"Most recently published": "Most recently published",
|
"Most recently published": "Most recently published",
|
||||||
"Least recently published": "Least recently published",
|
"Least recently published": "Least recently published",
|
||||||
"With the most participants": "With the most participants",
|
"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})",
|
"{timezoneLongName} ({timezoneShortName})": "{timezoneLongName} ({timezoneShortName})",
|
||||||
"{title} ({count} todos)": "{title} ({count} todos)",
|
"{title} ({count} todos)": "{title} ({count} todos)",
|
||||||
"{username} was invited to {group}": "{username} a été invité à {group}",
|
"{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>
|
<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-field :label="t('Language')" label-for="setting-language">
|
||||||
<o-select
|
<o-select
|
||||||
:loading="loadingTimezones || loadingUserSettings"
|
:loading="loadingTimezones || loadingUserSettings"
|
||||||
|
@ -120,7 +150,7 @@ import { Address, IAddress } from "@/types/address.model";
|
||||||
import { useTimezones } from "@/composition/apollo/config";
|
import { useTimezones } from "@/composition/apollo/config";
|
||||||
import { useUserSettings } from "@/composition/apollo/user";
|
import { useUserSettings } from "@/composition/apollo/user";
|
||||||
import { useHead } from "@vueuse/head";
|
import { useHead } from "@vueuse/head";
|
||||||
import { computed, defineAsyncComponent } from "vue";
|
import { computed, defineAsyncComponent, ref, watch } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { useMutation } from "@vue/apollo-composable";
|
import { useMutation } from "@vue/apollo-composable";
|
||||||
|
|
||||||
|
@ -140,6 +170,44 @@ useHead({
|
||||||
|
|
||||||
// langs: Record<string, string> = langs;
|
// 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({
|
const selectedTimezone = computed({
|
||||||
get() {
|
get() {
|
||||||
if (loggedUser.value?.settings?.timezone) {
|
if (loggedUser.value?.settings?.timezone) {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
content: ["./public/**/*.html", "./src/**/*.{vue,js,ts,jsx,tsx}"],
|
content: ["./public/**/*.html", "./src/**/*.{vue,js,ts,jsx,tsx}"],
|
||||||
|
darkMode: "class",
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
colors: {
|
colors: {
|
||||||
|
|
|
@ -7,6 +7,13 @@
|
||||||
<link rel="apple-touch-icon" href="/img/icons/apple-touch-icon-152x152.png" sizes="152x152" />
|
<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()} />
|
<link rel="mask-icon" href="/img/icons/safari-pinned-tab.svg" color={theme_color()} />
|
||||||
<meta name="theme-color" content={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 %>
|
<%= if is_root(assigns) do %>
|
||||||
<link rel="preload" href="/img/shape-1.svg" as="image" />
|
<link rel="preload" href="/img/shape-1.svg" as="image" />
|
||||||
<link rel="preload" href="/img/shape-2.svg" as="image" />
|
<link rel="preload" href="/img/shape-2.svg" as="image" />
|
||||||
|
|
Loading…
Reference in a new issue