Skip to content

Commit

Permalink
FIX: Light theme flash during page load (#8004)
Browse files Browse the repository at this point in the history
* FIX: Light theme flash during page load

* Invoke theme script inline
  • Loading branch information
flexdinesh authored Oct 14, 2022
1 parent 8eddc07 commit 709d1a8
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 107 deletions.
85 changes: 0 additions & 85 deletions docs/components/DarkModeBtn.tsx

This file was deleted.

4 changes: 2 additions & 2 deletions docs/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { Wrapper } from './primitives/Wrapper';
import { Hamburger } from './icons/Hamburger';
import { Button } from './primitives/Button';
import { NavItem } from './docs/Navigation';
import { DarkModeBtn } from './DarkModeBtn';
import { ThemeToggle } from './ThemeToggle';
import { Keystone } from './icons/Keystone';
import { MobileMenu } from './MobileMenu';
import { GitHub } from './icons/GitHub';
Expand Down Expand Up @@ -293,7 +293,7 @@ export function Header() {
>
Documentation
</Button>
<DarkModeBtn />
<ThemeToggle />
<a
href="https://github.com/keystonejs/keystone"
target="_blank"
Expand Down
21 changes: 2 additions & 19 deletions docs/components/Theme.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,16 @@
/** @jsxRuntime classic */
/** @jsx jsx */
import { jsx, Global } from '@emotion/react';
import { useState, useEffect } from 'react';

import { COLORS, SPACE, TYPE, TYPESCALE } from '../lib/TOKENS';

export function Theme() {
const [theme, setTheme] = useState<keyof typeof COLORS>('light');

useEffect(() => {
// we duplicate the logic of DarkModeBtn here so the flash is shorter
const detectedTheme =
(localStorage.getItem('theme') ||
window.matchMedia('(prefers-color-scheme: dark)').matches ||
'light') === 'dark'
? 'dark'
: 'light';
localStorage.setItem('theme', detectedTheme);

if (detectedTheme !== 'light') {
setTheme(detectedTheme);
}
}, []);

return (
<Global
styles={{
'[data-theme="light"]': { ...COLORS['light'] },
'[data-theme="dark"]': { ...COLORS['dark'] },
':root': {
...COLORS[theme],
...SPACE,
...TYPE,
...TYPESCALE,
Expand Down
71 changes: 71 additions & 0 deletions docs/components/ThemeToggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/** @jsxRuntime classic */
/** @jsx jsx */
import { Fragment, useState, useEffect, HTMLAttributes } from 'react';
import { jsx } from '@emotion/react';

import { COLORS } from '../lib/TOKENS';
import { LightMode } from './icons/LightMode';
import { DarkMode } from './icons/DarkMode';

function ModeIcon({ theme }: { theme: 'light' | 'dark' }) {
if (theme === 'dark') {
return <LightMode css={{ height: 'var(--space-xlarge)' }} />;
}

return <DarkMode css={{ height: 'var(--space-xlarge)' }} />;
}

export function ThemeToggle(props: HTMLAttributes<HTMLButtonElement>) {
/*
We don't want to render the toggle during server rendering
because Next will always server render the light mode toggle and hydrate the light mode toggle
even if the theme is dark mode based on system preference.
So we render the toggle only on the client
*/
const [theme, setTheme] = useState<keyof typeof COLORS | null>(null);

useEffect(() => {
const currentTheme = document.documentElement.getAttribute('data-theme') as 'light' | 'dark';
setTheme(currentTheme);
}, [setTheme]);

const handleThemeChange = () => {
const newTheme = theme === 'dark' ? 'light' : 'dark';
if (newTheme === 'dark') {
document.documentElement.setAttribute('data-theme', 'dark');
} else {
document.documentElement.setAttribute('data-theme', 'light');
}
setTheme(newTheme);
localStorage.setItem('theme', newTheme);
};

return (
<Fragment>
<button
key={theme}
onClick={handleThemeChange}
css={{
display: 'inline-flex',
appearance: 'none',
background: 'transparent',
boxShadow: 'none',
border: '0 none',
borderRadius: '100%',
lineHeight: 1,
padding: 0,
margin: 0,
color: 'var(--muted)',
cursor: 'pointer',
transition: 'color 0.3s ease',
'&:hover, &:focus': {
color: 'var(--link)',
},
}}
{...props}
>
{theme === null ? <span css={{ width: 24 }} /> : <ModeIcon theme={theme} />}
</button>
</Fragment>
);
}
31 changes: 30 additions & 1 deletion docs/pages/_document.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class MyDocument extends Document {

render() {
return (
<Html>
<Html data-theme="light">
<Head>
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
Expand All @@ -54,6 +54,35 @@ class MyDocument extends Document {
src="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.js"
/>
<script data-no-cookie data-respect-dnt async data-api="/_sb" src="/sb.js" />
{/*
The page is server rendered and hydrated on the browser client.
While server rendering we do not know what the user's preferred/saved theme is and default to light theme.
So we run this script in the browser before the page is rendered (not the react render but the browser render)
and set the theme class in html element so our styles would know which theme to paint on first paint.
All this to avoid a flash of default light theme when user either prefers dark theme or has previously
saved dark theme to their local storage. ¯\_(ツ)_/¯ React is hard sometimes.
*/}
<script
dangerouslySetInnerHTML={{
__html: `
(function () {
if (typeof window !== 'undefined') {
const isSystemColorSchemeDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
const localStorageTheme = localStorage.theme;
if (!localStorageTheme && isSystemColorSchemeDark) {
document.documentElement.setAttribute('data-theme', 'dark');
} else if (localStorageTheme === 'dark') {
document.documentElement.setAttribute('data-theme', 'dark');
} else {
// we already server render light theme
// so this is just ensuring that
document.documentElement.setAttribute('data-theme', 'light');
}
}
})();
`,
}}
/>
</Head>
<body>
<Main />
Expand Down

1 comment on commit 709d1a8

@vercel
Copy link

@vercel vercel bot commented on 709d1a8 Oct 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.