diff --git a/gatsby-ssr.js b/gatsby-ssr.js index b17b8fc1..41dfcfd4 100644 --- a/gatsby-ssr.js +++ b/gatsby-ssr.js @@ -3,5 +3,9 @@ * * See: https://www.gatsbyjs.org/docs/ssr-apis/ */ +import React from 'react'; +import HydrateTheme from './src/components/hydrate-theme'; -// You can delete this file if you're not using it +export const onRenderBody = ({ setPreBodyComponents }) => { + setPreBodyComponents([]); +}; diff --git a/package.json b/package.json index 5c51dabb..26b306be 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,6 @@ "@wkovacs64/normalize.css": "8.0.1", "axios": "0.18.0", "dotenv": "6.2.0", - "emotion-theming": "10.0.7", "gatsby": "2.0.118", "gatsby-plugin-emotion": "4.0.3", "gatsby-plugin-manifest": "2.0.17", @@ -56,9 +55,9 @@ "react-helmet": "6.0.0-beta", "react-icons": "3.3.0", "react-key-handler": "1.2.0-beta.3", - "react-use": "5.3.0", "typeface-nunito": "0.0.54", - "typeface-source-sans-pro": "0.0.54" + "typeface-source-sans-pro": "0.0.54", + "use-dark-mode": "2.2.2" }, "devDependencies": { "@babel/core": "7.2.2", diff --git a/src/@types/react-wait.d.ts b/src/@types/react-wait.d.ts deleted file mode 100644 index 71a54ba9..00000000 --- a/src/@types/react-wait.d.ts +++ /dev/null @@ -1 +0,0 @@ -declare module 'react-wait'; diff --git a/src/@types/use-callbag.d.ts b/src/@types/use-callbag.d.ts deleted file mode 100644 index 9908446e..00000000 --- a/src/@types/use-callbag.d.ts +++ /dev/null @@ -1 +0,0 @@ -declare module 'use-callbag'; diff --git a/src/@types/use-onclickoutside.d.ts b/src/@types/use-onclickoutside.d.ts deleted file mode 100644 index f0727c5e..00000000 --- a/src/@types/use-onclickoutside.d.ts +++ /dev/null @@ -1 +0,0 @@ -declare module 'use-onclickoutside'; diff --git a/src/components/alert-on-update.tsx b/src/components/alert-on-update.tsx index 1da7d4b8..63919b48 100644 --- a/src/components/alert-on-update.tsx +++ b/src/components/alert-on-update.tsx @@ -2,8 +2,9 @@ import React, { useState } from 'react'; import { StaticQuery, graphql } from 'gatsby'; import axios from 'axios'; import ms from 'ms'; -import styled from '../utils/styled'; +import styled from '@emotion/styled'; import isMobile from '../utils/is-mobile'; +import { light, dark } from '../theme'; import UpdatePoller from './update-poller'; import UpdateAlert from './update-alert'; @@ -12,15 +13,28 @@ const UpdateAlertContainer = styled.div` justify-content: center; border-style: solid; border-width: 0 0 1px; - border-color: ${({ theme }) => theme.colors.alertBorder}; - box-shadow: 4px 4px 8px 0px ${({ theme }) => theme.colors.alertShadow}; - color: ${({ theme }) => theme.colors.alertText}; - background-color: ${({ theme }) => theme.colors.alertBackground}; transition: color 0.3s ease, background-color 0.3s ease; - &:hover, - &:focus-within { - color: ${({ theme }) => theme.colors.alertBackground}; - background-color: ${({ theme }) => theme.colors.alertText}; + body.light-mode & { + border-color: ${light.colors.alertBorder}; + box-shadow: 4px 4px 8px 0px ${light.colors.alertShadow}; + color: ${light.colors.alertText}; + background-color: ${light.colors.alertBackground}; + &:hover, + &:focus-within { + color: ${light.colors.alertBackground}; + background-color: ${light.colors.alertText}; + } + } + body.dark-mode & { + border-color: ${dark.colors.alertBorder}; + box-shadow: 4px 4px 8px 0px ${dark.colors.alertShadow}; + color: ${dark.colors.alertText}; + background-color: ${dark.colors.alertBackground}; + &:hover, + &:focus-within { + color: ${dark.colors.alertBackground}; + background-color: ${dark.colors.alertText}; + } } `; diff --git a/src/components/footer.tsx b/src/components/footer.tsx index 7a48bb50..282c9514 100644 --- a/src/components/footer.tsx +++ b/src/components/footer.tsx @@ -1,12 +1,18 @@ import React from 'react'; import { css } from '@emotion/core'; +import styled from '@emotion/styled'; import { FaGithub } from 'react-icons/fa'; -import styled from '../utils/styled'; +import { light, dark } from '../theme'; const SourceLink = styled.a` - color: ${({ theme }) => theme.colors.pageText}; text-decoration: none; padding: 0.5rem; + body.light-mode & { + color: ${light.colors.pageText}; + } + body.dark-mode & { + color: ${dark.colors.pageText}; + } `; const SourceLinkIcon = styled(FaGithub)` diff --git a/src/components/header.tsx b/src/components/header.tsx index 96421bae..7e75cf8f 100644 --- a/src/components/header.tsx +++ b/src/components/header.tsx @@ -1,8 +1,9 @@ import React from 'react'; import { StaticQuery, graphql } from 'gatsby'; import { FiSun } from 'react-icons/fi'; -import styled from '../utils/styled'; +import styled from '@emotion/styled'; import mq from '../utils/mq'; +import { light, dark } from '../theme'; import AlertOnUpdate from './alert-on-update'; const HeaderContent = styled.section` @@ -20,10 +21,15 @@ const ThemeToggleButton = styled.button` top: 1rem; right: 1rem; cursor: pointer; - color: ${({ theme }) => theme.colors.pageText}; background-color: transparent; border: none; padding: 0.5rem; + body.light-mode & { + color: ${light.colors.pageText}; + } + body.dark-mode & { + color: ${dark.colors.pageText}; + } `; const ThemeToggleButtonIcon = styled(FiSun)` @@ -37,8 +43,6 @@ const H1 = styled.h1` font-family: 'Nunito', sans-serif; font-size: 2.25rem; font-variant: small-caps; - color: ${({ theme }) => theme.colors.headline}; - text-shadow: 1px 1px 1px ${({ theme }) => theme.colors.headlineShadow}; margin: 0; ${mq.md} { font-size: 3rem; @@ -47,6 +51,14 @@ const H1 = styled.h1` ${mq.lg} { font-size: 5rem; } + body.light-mode & { + color: ${light.colors.headline}; + text-shadow: 1px 1px 1px ${light.colors.headlineShadow}; + } + body.dark-mode & { + color: ${dark.colors.headline}; + text-shadow: 1px 1px 1px ${dark.colors.headlineShadow}; + } `; interface HeaderProps { diff --git a/src/components/hydrate-theme.tsx b/src/components/hydrate-theme.tsx new file mode 100644 index 00000000..1176fc43 --- /dev/null +++ b/src/components/hydrate-theme.tsx @@ -0,0 +1,49 @@ +import React from 'react'; + +// https://raw.githubusercontent.com/donavon/use-dark-mode/develop/noflash.js.txt +const hydrateThemeScript = ` + (function() { + // Change these if you use something different in your hook. + var storageKey = 'darkMode'; + var classNameDark = 'dark-mode'; + var classNameLight = 'light-mode'; + + function setClassOnDocumentBody(darkMode) { + document.body.classList.add(darkMode ? classNameDark : classNameLight); + document.body.classList.remove(darkMode ? classNameLight : classNameDark); + } + + var preferDarkQuery = '(prefers-color-scheme: dark)'; + var mql = window.matchMedia(preferDarkQuery); + var supportsColorSchemeQuery = mql.media === preferDarkQuery; + var localStorageTheme = null; + try { + localStorageTheme = localStorage.getItem(storageKey); + } catch (err) {} + var localStorageExists = localStorageTheme !== null; + if (localStorageExists) { + localStorageTheme = JSON.parse(localStorageTheme); + } + + // Determine the source of truth + if (localStorageExists) { + // source of truth from localStorage + setClassOnDocumentBody(localStorageTheme); + } else if (supportsColorSchemeQuery) { + // source of truth from system + setClassOnDocumentBody(mql.matches); + localStorage.setItem(storageKey, mql.matches); + } else { + // source of truth from document.body + var isDarkMode = document.body.classList.contains(classNameDark); + localStorage.setItem(storageKey, JSON.stringify(isDarkMode)); + } + })(); +`; + +const HydrateTheme: React.FunctionComponent = () => ( + // eslint-disable-next-line react/no-danger +