-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: move logic from providers to site
- Loading branch information
Showing
11 changed files
with
154 additions
and
101 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './theme.js'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import { Theme } from '@myst-theme/common'; | ||
|
||
export function postThemeToAPI(theme: Theme) { | ||
const xmlhttp = new XMLHttpRequest(); | ||
xmlhttp.open('POST', '/api/theme'); | ||
xmlhttp.setRequestHeader('Content-Type', 'application/json;charset=UTF-8'); | ||
xmlhttp.send(JSON.stringify({ theme })); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { THEME_LOCALSTORAGE_KEY } from '../hooks/theme.js'; | ||
const PREFERS_LIGHT_MQ = '(prefers-color-scheme: light)'; | ||
|
||
/** | ||
* A blocking element that runs on the client before hydration to update the <html> preferred class | ||
* This ensures that the hydrated state matches the non-hydrated state (by updating the DOM on the | ||
* client between SSR on the server and hydration on the client) | ||
*/ | ||
export function BlockingThemeLoader({ useLocalStorage }: { useLocalStorage: boolean }) { | ||
const LOCAL_STORAGE_SOURCE = `localStorage.getItem(${JSON.stringify(THEME_LOCALSTORAGE_KEY)})`; | ||
const CLIENT_THEME_SOURCE = ` | ||
const savedTheme = ${useLocalStorage ? LOCAL_STORAGE_SOURCE : 'null'}; | ||
const theme = window.matchMedia(${JSON.stringify(PREFERS_LIGHT_MQ)}).matches ? 'light' : 'dark'; | ||
const classes = document.documentElement.classList; | ||
const hasAnyTheme = classes.contains('light') || classes.contains('dark'); | ||
if (!hasAnyTheme) classes.add(savedTheme ?? theme); | ||
`; | ||
|
||
return <script dangerouslySetInnerHTML={{ __html: CLIENT_THEME_SOURCE }} />; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './theme.js'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import React, { useEffect, useRef } from 'react'; | ||
import { Theme } from '@myst-theme/common'; | ||
import { isTheme } from '@myst-theme/providers'; | ||
const PREFERS_LIGHT_MQ = '(prefers-color-scheme: light)'; | ||
import { postThemeToAPI } from '../actions/theme.js'; | ||
|
||
export const THEME_LOCALSTORAGE_KEY = 'myst:theme'; | ||
|
||
export function getPreferredTheme() { | ||
if (typeof window !== 'object') { | ||
return null; | ||
} | ||
const mediaQuery = window.matchMedia(PREFERS_LIGHT_MQ); | ||
return mediaQuery.matches ? Theme.light : Theme.dark; | ||
} | ||
|
||
/** | ||
* Hook that changes theme to follow changes to system preference. | ||
*/ | ||
export function usePreferredTheme({ setTheme }: { setTheme: (theme: Theme | null) => void }) { | ||
// Listen for system-updates that change the preferred theme | ||
// This will modify the saved theme | ||
useEffect(() => { | ||
const mediaQuery = window.matchMedia(PREFERS_LIGHT_MQ); | ||
const handleChange = () => { | ||
setTheme(mediaQuery.matches ? Theme.light : Theme.dark); | ||
}; | ||
mediaQuery.addEventListener('change', handleChange); | ||
return () => mediaQuery.removeEventListener('change', handleChange); | ||
}, []); | ||
} | ||
|
||
export function useTheme({ | ||
ssrTheme, | ||
useLocalStorage, | ||
}: { | ||
ssrTheme: Theme | null; | ||
useLocalStorage?: boolean; | ||
}) { | ||
// Here, the initial state on the server without any set cookies will be null. | ||
// The client will then load the initial state as non-null. | ||
// Thus, we must mutate the DOM *pre-hydration* to ensure that the initial state is | ||
// identical to that of the hydrated state, i.e. perform out-of-react DOM updates | ||
// This is handled by the BlockingThemeLoader component. | ||
const [theme, setTheme] = React.useState<Theme | null>(() => { | ||
if (isTheme(ssrTheme)) { | ||
return ssrTheme; | ||
} | ||
// On the server we can't know what the preferred theme is, so leave it up to client | ||
if (typeof window !== 'object') { | ||
return null; | ||
} | ||
// System preferred theme | ||
const preferredTheme = getPreferredTheme(); | ||
|
||
// Local storage preferred theme | ||
const savedTheme = localStorage.getItem(THEME_LOCALSTORAGE_KEY); | ||
return useLocalStorage && isTheme(savedTheme) ? savedTheme : preferredTheme; | ||
}); | ||
|
||
// Listen for system-updates that change the preferred theme | ||
usePreferredTheme({ setTheme }); | ||
|
||
// Listen for changes to theme, and propagate to server | ||
// This should be unidirectional; updates to the cookie do not trigger document rerenders | ||
const mountRun = useRef(false); | ||
useEffect(() => { | ||
// Only update after the component is mounted (i.e. don't send initial state) | ||
if (!mountRun.current) { | ||
mountRun.current = true; | ||
return; | ||
} | ||
if (!isTheme(theme)) { | ||
return; | ||
} | ||
if (useLocalStorage) { | ||
localStorage.setItem(THEME_LOCALSTORAGE_KEY, theme); | ||
} else { | ||
postThemeToAPI(theme); | ||
} | ||
}, [theme]); | ||
|
||
return [theme, setTheme]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,8 @@ | ||
export * from './utils.js'; | ||
export * from './loaders/index.js'; | ||
export * from './components/index.js'; | ||
export * from './hooks/index.js'; | ||
export * from './pages/index.js'; | ||
export * from './seo/index.js'; | ||
export * from './themeCSS.js'; | ||
export * from './actions/index.js'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters