Beautiful theme switch

This commit is contained in:
Daniel Kluge 2022-10-16 15:59:08 +02:00
parent 2b3c4e2482
commit 068a3f8f1e
8 changed files with 122 additions and 37 deletions

View File

@ -10,15 +10,6 @@ interface ILayoutProps {
} }
const Layout: NextPage<ILayoutProps> = ({ title, children }) => { const Layout: NextPage<ILayoutProps> = ({ title, children }) => {
useEffect(() => {
if (typeof window !== "undefined") {
const theme = window.localStorage.getItem("theme");
if(!theme || !document) return;
document.documentElement.setAttribute("data-theme", theme);
}
}, []);
return <> return <>
<Head> <Head>
<title>{title ?? "c0ntroller.de"}</title> <title>{title ?? "c0ntroller.de"}</title>

View File

@ -2,20 +2,11 @@
import type { NextPage } from "next"; import type { NextPage } from "next";
import Link from "next/link"; import Link from "next/link";
import { Terminal, Sun, Moon } from "phosphor-react"; import { Terminal, Sun, Moon } from "phosphor-react";
import ThemeSwitch from "./ThemeSwitch";
import styles from "../../styles/Blog/Navigation.module.scss"; import styles from "../../styles/Blog/Navigation.module.scss";
const Navigation: NextPage<{}> = () => { const Navigation: NextPage<{}> = () => {
const switchTheme = () => {
if (typeof document === "undefined") return;
const current = document.documentElement.getAttribute("data-theme") || "dark";
const setTo = current === "dark" ? "light" : "dark";
document.documentElement.setAttribute("data-theme", setTo);
if (typeof window !== "undefined") window.localStorage.setItem("theme", setTo);
};
return <nav className={styles.navigation}> return <nav className={styles.navigation}>
<Link href={"/"}> <Link href={"/"}>
<a className={`nostyle ${styles.imgContainer}`}> <a className={`nostyle ${styles.imgContainer}`}>
@ -29,10 +20,7 @@ const Navigation: NextPage<{}> = () => {
<div className={styles.navLink}><Link href={"/"}><a className="nostyle">About me</a></Link></div> <div className={styles.navLink}><Link href={"/"}><a className="nostyle">About me</a></Link></div>
<div className={styles.spacer}></div> <div className={styles.spacer}></div>
<Terminal size={"1.5em"} /> <Terminal size={"1.5em"} />
<div className={styles.themeSwitch}> <ThemeSwitch />
<Sun className={styles.lightTheme} size={"1.5em"} onClick={switchTheme} />
<Moon className={styles.darkTheme} size={"1.5em"} onClick={switchTheme} />
</div>
</nav>; </nav>;
}; };

View File

@ -0,0 +1,53 @@
import type { NextPage } from "next";
import { useEffect, useState } from "react";
import { useTheme } from "next-themes";
import { Sun, Moon, FileJs } from "phosphor-react";
import styles from "../../styles/Blog/ThemeSwitch.module.scss";
interface FadeProperties {
sun?: string;
moon?: string;
}
const ThemeSwitch: NextPage<{ size?: string }> = ({ size }) => {
const [mounted, setMounted] = useState(false);
const [fadeProps, setFadeProps] = useState<FadeProperties>({});
const { theme, setTheme } = useTheme();
// Will be run when the component is rendered.
useEffect(() => {
setMounted(true);
}, []);
const switchTheme = (theme: string) => {
if (theme === "dark") setFadeProps({
sun: styles.fadeIn,
moon: styles.fadeOut
});
else setFadeProps({
sun: styles.fadeOut,
moon: styles.fadeIn
});
setTheme(theme);
};
if (!mounted) {
return <div className={styles.switch} title="Theme switching needs JS to be enabled.">
<FileJs size={size || "1.5em"} />
</div>;
}
const sunClasses = fadeProps.sun || (theme === "dark" ? styles.selected : undefined);
const moonClasses = fadeProps.moon || (theme === "light" ? styles.selected : undefined);
return <div className={styles.switch}>
<Sun size={size || "1.5em"} className={sunClasses} onClick={() => switchTheme("light")} />
<Moon size={size || "1.5em"} className={moonClasses} onClick={() => switchTheme("dark")} />
</div>;
};
export default ThemeSwitch;

17
package-lock.json generated
View File

@ -10,6 +10,7 @@
"color": "^4.2.3", "color": "^4.2.3",
"highlight.js": "^11.5.1", "highlight.js": "^11.5.1",
"next": "12.1.0", "next": "12.1.0",
"next-themes": "^0.2.1",
"node-fetch": "^3.2.0", "node-fetch": "^3.2.0",
"phosphor-react": "^1.3.1", "phosphor-react": "^1.3.1",
"react": "17.0.2", "react": "17.0.2",
@ -3075,6 +3076,16 @@
} }
} }
}, },
"node_modules/next-themes": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.2.1.tgz",
"integrity": "sha512-B+AKNfYNIzh0vqQQKqQItTS8evEouKD7H5Hj3kmuPERwddR2TxvDSFZuTj6T7Jfn1oyeUyJMydPl1Bkxkh0W7A==",
"peerDependencies": {
"next": "*",
"react": "*",
"react-dom": "*"
}
},
"node_modules/node-domexception": { "node_modules/node-domexception": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
@ -6708,6 +6719,12 @@
"use-subscription": "1.5.1" "use-subscription": "1.5.1"
} }
}, },
"next-themes": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.2.1.tgz",
"integrity": "sha512-B+AKNfYNIzh0vqQQKqQItTS8evEouKD7H5Hj3kmuPERwddR2TxvDSFZuTj6T7Jfn1oyeUyJMydPl1Bkxkh0W7A==",
"requires": {}
},
"node-domexception": { "node-domexception": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",

View File

@ -12,6 +12,7 @@
"color": "^4.2.3", "color": "^4.2.3",
"highlight.js": "^11.5.1", "highlight.js": "^11.5.1",
"next": "12.1.0", "next": "12.1.0",
"next-themes": "^0.2.1",
"node-fetch": "^3.2.0", "node-fetch": "^3.2.0",
"phosphor-react": "^1.3.1", "phosphor-react": "^1.3.1",
"react": "17.0.2", "react": "17.0.2",

View File

@ -1,5 +1,6 @@
import type { AppProps } from "next/app"; import type { AppProps } from "next/app";
import Head from "next/head"; import Head from "next/head";
import { ThemeProvider } from "next-themes";
import "../styles/globals.scss"; import "../styles/globals.scss";
import { CommandsProvider } from "../lib/commands/ContextProvider"; import { CommandsProvider } from "../lib/commands/ContextProvider";
import { ModalFunctionProvider } from "../components/Terminal/contexts/ModalFunctions"; import { ModalFunctionProvider } from "../components/Terminal/contexts/ModalFunctions";
@ -33,11 +34,13 @@ function MyApp({ Component, pageProps }: AppProps) {
<meta name="msapplication-TileImage" content="/mstile-310x310.png" /> <meta name="msapplication-TileImage" content="/mstile-310x310.png" />
<meta name="theme-color" content="#444444" /> <meta name="theme-color" content="#444444" />
</Head> </Head>
<ThemeProvider>
<CommandsProvider> <CommandsProvider>
<ModalFunctionProvider> <ModalFunctionProvider>
<Component {...pageProps} /> <Component {...pageProps} />
</ModalFunctionProvider> </ModalFunctionProvider>
</CommandsProvider> </CommandsProvider>
</ThemeProvider>
</>; </>;
} }

View File

@ -54,13 +54,4 @@
.spacer { .spacer {
flex-grow: 2; flex-grow: 2;
} }
.themeSwitch {
.lightTheme {
display: var(--blog_dark-el-display)
}
.darkTheme {
display: var(--blog_light-el-display)
}
}
} }

View File

@ -0,0 +1,41 @@
.switch {
position: relative;
width: 1.5em;
height: 1.5em;
& > * {
position: absolute;
}
.fadeOut {
animation: fadeOut 0.2s ease-in-out;
animation-fill-mode: forwards;
}
.fadeIn {
animation: fadeIn 0.2s ease-in-out;
animation-fill-mode: forwards;
}
}
@keyframes fadeOut {
from {
opacity: 1;
transform: translate(0, 0);
}
to {
opacity: 0;
transform: translate(0, -100%);
}
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translate(0, 100%);
}
to {
opacity: 1;
transform: translate(0, 0);
}
}