lpubsppop01's site
I made color scheme changing to work in all pages in this site. I confused because that there seems to be various implementation methods for sharing context. Finally worked code is following (fixed on Feb 13, 2023).
// components/app-context.tsx
import { createContext, Dispatch, SetStateAction } from "react";
export type AppContextValue = {
getColorScheme: () => string | null,
setColorScheme: Dispatch<SetStateAction<string | null>>,
}
export const AppContext = createContext<AppContextValue>({
getColorScheme: () => null,
setColorScheme: (_) => { }
})
// components/top-bar.tsx
...
export default function TopBar() {
...
const appContextValue = useContext(AppContext)
const handleColorSchemeButtonClick: React.MouseEventHandler<HTMLButtonElement> = (_) => {
if (appContextValue.getColorScheme() == 'dark') {
document.documentElement.setAttribute('my-color-scheme', 'light')
appContextValue.setColorScheme('light')
} else {
document.documentElement.setAttribute('my-color-scheme', 'dark')
appContextValue.setColorScheme('dark')
}
};
return (
<>
...
<button
className={appContextValue.getColorScheme() == 'dark' ? styles.lightMode : styles.darkMode}
onClick={handleColorSchemeButtonClick}>
<Image
src={
appContextValue.getColorScheme() == 'dark'
? '/material_icons/light_mode_black_24dp.svg'
: '/material_icons/dark_mode_white_24dp.svg'
}
alt={appContextValue.getColorScheme() == 'dark' ? 'Light mode' : 'Dark mode'}
width={d.ICON_SMALL} height={d.ICON_SMALL}
/>
</button>
...
</>
)
// pages/_app.tsx
...
export default function App({ Component, pageProps }: AppProps) {
const [colorScheme, setColorScheme] = useState<string | null>(null)
const appContextValue: AppContextValue = {
getColorScheme: () => colorScheme,
setColorScheme: (value) => {
setColorScheme(value)
localStorage.setItem('colorScheme', value as string)
}
}
useEffect(() => {
let colorScheme = appContextValue.getColorScheme()
if (colorScheme == null) {
let loadedColorScheme = localStorage.getItem('colorScheme')
let setsDark = loadedColorScheme == 'dark'
if (loadedColorScheme == null) {
setsDark = window.matchMedia('(prefers-color-scheme: dark)').matches
}
if (setsDark) {
document.documentElement.setAttribute('my-color-scheme', 'dark')
// Detect Safari
const lowerUserAgent = window.navigator.userAgent.toLowerCase()
const browserIsSafari =
lowerUserAgent.indexOf('chrome') == -1 &&
lowerUserAgent.indexOf('safari') != -1
// Set color scheme to app context
if (browserIsSafari) {
// Set with a delay on Safari to ensure updates
setTimeout(() => {
appContextValue.setColorScheme('dark')
}, 200)
} else {
// Set normally on other browsers
appContextValue.setColorScheme('dark')
}
}
}
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
if (e.matches && appContextValue.getColorScheme() != 'dark') {
document.documentElement.setAttribute('my-color-scheme', 'dark')
appContextValue.setColorScheme('dark')
} else {
document.documentElement.setAttribute('my-color-scheme', 'light')
appContextValue.setColorScheme('light')
}
})
});
...
return (
<AppContext.Provider value={appContextValue}>
<Component {...pageProps} />
</AppContext.Provider>
)
}
The color scheme getter was the property colorScheme at first, but misuse of setter took time.
So I changed it to the function getColorScheme().
It may be not good for performance, but used the above this time.
References: