Persistent color theme
This commit is contained in:
@@ -47,7 +47,6 @@ export default defineNuxtConfig({
|
||||
},
|
||||
piniaPluginPersistedstate: {
|
||||
debug: process.env.NODE_ENV === "development", // log error to console
|
||||
storage: "cookies",
|
||||
cookieOptions: {
|
||||
sameSite: "lax", // prevent CSRF
|
||||
secure: process.env.NODE_ENV !== "development" // only send over HTTPS
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 12 KiB |
@@ -12,6 +12,9 @@ import { useStateStore } from "@/stores/stateStore";
|
||||
const router = useRouter();
|
||||
const store = useStateStore();
|
||||
|
||||
// Set dark theme
|
||||
store.applyColorMode();
|
||||
|
||||
useHead({
|
||||
titleTemplate: (titleChunk: string | undefined): string | null => {
|
||||
return titleChunk ? `${titleChunk} - website-vue` : 'website-vue';
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
<!-- https://github.com/barelyhuman/snips/blob/dev/pages/css-loader.md -->
|
||||
<div class="loader"></div>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.dark {
|
||||
background-color: #1a1d20;
|
||||
}
|
||||
|
||||
.loader {
|
||||
display: block;
|
||||
position: fixed;
|
||||
@@ -21,6 +31,13 @@
|
||||
animation: loader 400ms linear infinite;
|
||||
}
|
||||
|
||||
.dark .loader {
|
||||
border-top-color: #fff !important;
|
||||
border-left-color: #fff !important;
|
||||
border-bottom-color: #444 !important;
|
||||
border-right-color: #444 !important;
|
||||
}
|
||||
|
||||
@-webkit-keyframes loader {
|
||||
0% {
|
||||
-webkit-transform: translate(-50%, -50%) rotate(0deg);
|
||||
@@ -39,3 +56,30 @@
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="loader"></div>
|
||||
<script>
|
||||
let css = "light";
|
||||
let state = null;
|
||||
|
||||
const json = localStorage.getItem("state");
|
||||
if (json !== null) {
|
||||
state = JSON.parse(json).colorMode;
|
||||
}
|
||||
|
||||
if (state === null || state === "auto") {
|
||||
const preferDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
if (preferDark) {
|
||||
css = "dark";
|
||||
}
|
||||
}
|
||||
else if (state === "dark") {
|
||||
css = "dark";
|
||||
}
|
||||
|
||||
const element = document.querySelector("html");
|
||||
element.classList.add(css);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Vendored
+2
@@ -10,6 +10,7 @@ declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
IBi0Circle: typeof import('~icons/bi/0-circle')['default']
|
||||
IBiActivity: typeof import('~icons/bi/activity')['default']
|
||||
IFaAdjust: typeof import('~icons/fa/adjust')['default']
|
||||
IFaAngellist: typeof import('~icons/fa/angellist')['default']
|
||||
IFaCheck: typeof import('~icons/fa/check')['default']
|
||||
IFaClone: typeof import('~icons/fa/clone')['default']
|
||||
@@ -22,6 +23,7 @@ declare module 'vue' {
|
||||
IFaLink: typeof import('~icons/fa/link')['default']
|
||||
IFaLinkedinSquare: typeof import('~icons/fa/linkedin-square')['default']
|
||||
IFaMoonO: typeof import('~icons/fa/moon-o')['default']
|
||||
IFaSolidMoon: typeof import('~icons/fa-solid/moon')['default']
|
||||
IFaSolidSun: typeof import('~icons/fa-solid/sun')['default']
|
||||
IMdiAccountBox: typeof import('~icons/mdi/account-box')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<div class="dropup position-fixed corner">
|
||||
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<template v-if="store.colorMode === 'light'">
|
||||
<IFaSolidSun />
|
||||
</template>
|
||||
<template v-else-if="store.colorMode === 'dark'">
|
||||
<IFaSolidMoon />
|
||||
</template>
|
||||
<template v-else>
|
||||
<IFaAdjust />
|
||||
</template>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li>
|
||||
<a class="dropdown-item" :class="store.colorMode === 'light' ? 'active' : ''" @click="store.setColorMode('light')">
|
||||
<IFaSolidSun /> Light
|
||||
<IFaCheck v-if="store.colorMode === 'light'" class="font-smaller float-right" />
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item" :class="store.colorMode === 'dark' ? 'active' : ''" @click="store.setColorMode('dark')">
|
||||
<IFaSolidMoon /> Dark
|
||||
<IFaCheck v-if="store.colorMode === 'dark'" class="font-smaller float-right" />
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item" :class="store.colorMode === 'auto' ? 'active' : ''" @click="store.setColorMode('auto')">
|
||||
<IFaAdjust width="1.3em" /> Auto
|
||||
<IFaCheck v-if="store.colorMode === 'auto'" class="font-smaller float-right" />
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.corner {
|
||||
right: 10px;
|
||||
bottom: 10px;
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
min-width: 125px;
|
||||
}
|
||||
|
||||
.font-smaller {
|
||||
font-size: .6rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useStateStore } from "@/stores/stateStore";
|
||||
|
||||
const store = useStateStore();
|
||||
</script>
|
||||
@@ -1,23 +0,0 @@
|
||||
<template>
|
||||
<div class="position-fixed corner">
|
||||
<template v-if="store.colorMode === 'dark'">
|
||||
<IFaSolidSun @click="store.toggleColorMode" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<IFaMoonO @click="store.toggleColorMode" />
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.corner {
|
||||
right: 10px;
|
||||
bottom: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useStateStore } from "@/stores/stateStore";
|
||||
|
||||
const store = useStateStore();
|
||||
</script>
|
||||
@@ -10,7 +10,7 @@
|
||||
</div>
|
||||
<SharedFooter />
|
||||
</div>
|
||||
<ToggleColorMode />
|
||||
<ColorMode />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
+27
-11
@@ -6,21 +6,34 @@ import bootstrap from "bootstrap/dist/js/bootstrap.bundle.min";
|
||||
|
||||
export const useStateStore = defineStore("state", () => {
|
||||
|
||||
const colorMode = ref<string>("light");
|
||||
const toggleColorMode = (): void => {
|
||||
colorMode.value = colorMode.value === "dark" ? "light" : "dark";
|
||||
const html = document.querySelector("html") as HTMLElement;
|
||||
const colorMode = ref<string>("auto");
|
||||
const setColorMode = (newMode: string): void => {
|
||||
colorMode.value = newMode;
|
||||
applyColorMode();
|
||||
};
|
||||
const applyColorMode = (): void => {
|
||||
let css = "light";
|
||||
if (colorMode.value === "auto") {
|
||||
const preferDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
if (preferDark) {
|
||||
css = "dark";
|
||||
}
|
||||
}
|
||||
else if (colorMode.value == "dark") {
|
||||
css = "dark";
|
||||
}
|
||||
|
||||
const html = document.documentElement as HTMLElement;
|
||||
|
||||
// Theme used by Bootstrap
|
||||
html.setAttribute('data-bs-theme', colorMode.value);
|
||||
html.setAttribute('data-bs-theme', css);
|
||||
|
||||
// Theme class used by shiki syntax highlighting
|
||||
if (colorMode.value === "dark") {
|
||||
html.classList.add("dark");
|
||||
} else {
|
||||
html.classList.remove("dark");
|
||||
if (css === "dark") {
|
||||
html.classList.add("dark");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const popoverList = ref<any[]>([]);
|
||||
const tooltipList = ref<any[]>([]);
|
||||
@@ -37,7 +50,10 @@ export const useStateStore = defineStore("state", () => {
|
||||
tooltipList.value = [...tooltipTriggerList].map(tooltip => new bootstrap.Tooltip(tooltip));
|
||||
};
|
||||
|
||||
return { colorMode, toggleColorMode, initBootstrap }
|
||||
return { colorMode, setColorMode, applyColorMode, initBootstrap }
|
||||
}, {
|
||||
persist: process.env.NODE_ENV === 'development' ? true : false,
|
||||
persist: {
|
||||
pick: ["colorMode"],
|
||||
storage: localStorage
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user