Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added dark mode support #701

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 28 additions & 2 deletions components/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,21 @@ type Props = {
children: ReactNode;
className?: string;
viewBox?: string;
width?: number | `${number}`;
height?: number | `${number}`;
};

const Icon: FC<Props> = ({
children,
className = "",
viewBox = DEFAULT_VIEW_BOX,
width = "45",
height = "45"
}) => (
<span className={`icon ${className}`}>
<svg
width="45"
height="45"
width={width}
height={height}
viewBox={viewBox}
xmlns="http://www.w3.org/2000/svg"
>
Expand Down Expand Up @@ -66,3 +70,25 @@ export function DiscordIcon({ className = "" }) {
</Icon>
);
}

export function LightThemeIcon({ className = "" }) {
return (
<Icon className={className} width={"24"} height={"24"} viewBox={"0 0 16 16"}>
<path
fill="#ffffff"
d="M8 11a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0 1a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z"
/>
</Icon>
);
}

export function DarkThemeIcon({ className = "" }) {
return (
<Icon className={className} width={"24"} height={"24"} viewBox={"0 0 24 24"}>
<path
fill="#ffffff"
d="M12.0972 2.53039C12.2913 2.8649 12.2752 3.28136 12.0557 3.5998C11.3898 4.56594 11 5.73595 11 7.00002C11 10.3137 13.6863 13 17 13C18.2641 13 19.4341 12.6102 20.4002 11.9443C20.7187 11.7249 21.1351 11.7087 21.4696 11.9028C21.8041 12.0969 21.9967 12.4665 21.9642 12.8519C21.5313 17.9765 17.236 22 12 22C6.47715 22 2 17.5229 2 12C2 6.76398 6.02351 2.46874 11.1481 2.03585C11.5335 2.0033 11.9031 2.19588 12.0972 2.53039ZM9.42424 4.42352C6.26994 5.49553 4 8.48306 4 12C4 16.4183 7.58172 20 12 20C15.517 20 18.5045 17.7301 19.5765 14.5758C18.7676 14.8508 17.9008 15 17 15C12.5817 15 9 11.4183 9 7.00002C9 6.09922 9.1492 5.2324 9.42424 4.42352Z"
/>
</Icon>
);
}
3 changes: 3 additions & 0 deletions components/nav.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { FC, useCallback, useState } from "react";
import classnames from "classnames";
import SocialLinks from "./social-links";
import ToggleTheme from "./toggle-theme";

// TODO: what is this thing??
type Blog = any;
Expand Down Expand Up @@ -68,6 +69,8 @@ const Navigation: FC<{ blog: Blog }> = ({ blog }) => {
<div className="navbar-end">
<Links blog={blog} />

<ToggleTheme />

<hr className="is-hidden-mobile" />

<SocialLinks />
Expand Down
33 changes: 33 additions & 0 deletions components/toggle-theme.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from "react";
import { DarkThemeIcon, LightThemeIcon } from "./icons";

const ToggleTheme: React.FC = () => {
/**
* Handles theme change
*/
const toggleTheme = () => {
const THEME_KEY = 'data-theme';

// getting current theme from body element
const currentTheme = document.body.getAttribute(THEME_KEY);

// new theme, opposite to current theme
const newTheme = currentTheme === 'light' ? 'dark' : 'light';

// set new theme on body element
document.body.setAttribute(THEME_KEY, newTheme);
// set new theme in local storage
localStorage.setItem(THEME_KEY, newTheme);
};

return (
<div className="toggle-theme__container">
<button className="toggle-theme__btn" onClick={toggleTheme}>
<LightThemeIcon className="toggle-theme__icon--light" />
<DarkThemeIcon className="toggle-theme__icon--dark" />
</button>
</div>
);
};

export default ToggleTheme;
54 changes: 54 additions & 0 deletions hooks/use-theme.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import React from "react";

// This supresses the `useLayoutEffect` usage warning on server side
if (typeof window === 'undefined') {
React.useLayoutEffect = () => {}
}

let initDone = false;

export const useTheme = () => {

// This effect selects the theme from preferences or storage
React.useLayoutEffect(() => {
if (initDone) {
// return early if the effect has ran once
return;
}
initDone = true;

// getting theme value from local storage
const savedTheme = localStorage.getItem('data-theme');
if (savedTheme) {
// if user has theme in localStorage, set it on body element
document.body.setAttribute('data-theme', savedTheme);
return;
}

// When localStorage does not contain theme value
// Read user color preference on device
const darkModePreferred = matchMedia('(prefers-color-scheme: dark)').matches;
// set theme value on body element as per device preference
document.body.setAttribute('data-theme', darkModePreferred ? 'dark' : 'light');
}, []);


// This effects adds the change listener on "prefers-color-scheme: dark" media query
React.useEffect(() => {
// Preferred Theme Media Query
const themeMediaQuery = matchMedia('(prefers-color-scheme: dark)');

// Handles preferred color scheme change
function onPreferColorSchemeChange(event: MediaQueryListEvent) {
// Remove saved theme data in localStorage on change of theme preference
localStorage.removeItem('data-theme');
// Setting new preferred theme on body element
document.body.setAttribute('data-theme', event.matches ? 'dark' : 'light');
}

themeMediaQuery.addEventListener("change", onPreferColorSchemeChange);
return () => {
themeMediaQuery.removeEventListener("change", onPreferColorSchemeChange);
}
}, []);
}
4 changes: 4 additions & 0 deletions pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@ import React from "react";
import { AppProps } from "next/app";

import "../styles/styles.scss";
import { useTheme } from "../hooks/use-theme";

function MyApp({ Component, pageProps }: AppProps) {
// Handle Theme Selection on client side
useTheme();

return <Component {...pageProps} />;
}

Expand Down
Loading
Loading