feat: add auto mode in theme selection

This commit is contained in:
QiShaoXuan
2022-09-28 10:49:02 +08:00
parent fb828c9cf4
commit 008c0d83e3
4 changed files with 39 additions and 28 deletions
+7
View File
@@ -21,6 +21,13 @@ const Home: NextPage = () => {
> >
current theme mode :{mode}(click to change) current theme mode :{mode}(click to change)
</button> </button>
<button
onClick={() => {
changeMode('auto');
}}
>
click to set "auto" mode
</button>
</div> </div>
); );
}; };
+25 -24
View File
@@ -3,11 +3,16 @@ import {
Global, Global,
css, css,
} from '@emotion/react'; } from '@emotion/react';
import { createContext, useEffect, useRef, useState } from 'react'; import { createContext, useEffect, useState } from 'react';
import type { PropsWithChildren } from 'react'; import type { PropsWithChildren } from 'react';
import { ThemeMode, ThemeProviderProps, ThemeProviderValue } from './types'; import {
Theme,
ThemeMode,
ThemeProviderProps,
ThemeProviderValue,
} from './types';
import { lightTheme, darkTheme, globalThemeConstant } from './theme'; import { lightTheme, darkTheme, globalThemeConstant } from './theme';
import { SystemTheme, LocalStorageThemeMode } from '@/styles/utils'; import { SystemTheme, localStorageThemeMode } from '@/styles/utils';
export const ThemeContext = createContext<ThemeProviderValue>({ export const ThemeContext = createContext<ThemeProviderValue>({
mode: 'light', mode: 'light',
@@ -16,57 +21,53 @@ export const ThemeContext = createContext<ThemeProviderValue>({
}); });
export const ThemeProvider = ({ export const ThemeProvider = ({
defaultThemeMode = 'light', defaultTheme = 'light',
children, children,
}: PropsWithChildren<ThemeProviderProps>) => { }: PropsWithChildren<ThemeProviderProps>) => {
const { current: localStorageThemeMode } = useRef( const [theme, setTheme] = useState<Theme>(defaultTheme);
new LocalStorageThemeMode() const [mode, setMode] = useState<ThemeMode>('auto');
);
const [mode, setMode] = useState<ThemeMode>(defaultThemeMode);
const theme = mode === 'dark' ? darkTheme : lightTheme;
const themeStyle = theme === 'light' ? lightTheme : darkTheme;
const changeMode = (themeMode: ThemeMode) => { const changeMode = (themeMode: ThemeMode) => {
setMode(themeMode); themeMode !== mode && setMode(themeMode);
// Remember the theme mode which user selected for next time // Remember the theme mode which user selected for next time
localStorageThemeMode.set(themeMode); localStorageThemeMode.set(themeMode);
}; };
useEffect(() => { useEffect(() => {
const systemTheme = new SystemTheme(); const systemTheme = new SystemTheme();
// TODO: System theme mode and user theme mode which selected need to be prioritized
// If user has selected a theme mode, use it
const selectedThemeMode = localStorageThemeMode.get(); const selectedThemeMode = localStorageThemeMode.get();
if (selectedThemeMode) {
defaultThemeMode !== selectedThemeMode && setMode(selectedThemeMode); const themeMode = selectedThemeMode || mode;
if (themeMode === 'auto') {
setTheme(systemTheme.get());
} else { } else {
// If user has not selected a theme mode, use system theme setTheme(themeMode);
const systemThemeMode = systemTheme.get();
defaultThemeMode !== systemThemeMode && setMode(systemThemeMode);
} }
// When system theme changed, change the theme mode // When system theme changed, change the theme mode
systemTheme.onChange(() => { systemTheme.onChange(() => {
// TODO: There may be should be provided a way to let user choose whether to // TODO: There may be should be provided a way to let user choose whether to
setMode(systemTheme.get()); if (mode === 'auto') {
setTheme(systemTheme.get());
}
}); });
return () => { return () => {
systemTheme.dispose(); systemTheme.dispose();
}; };
}, []); }, [mode]);
return ( return (
<ThemeContext.Provider value={{ mode, changeMode, theme }}> <ThemeContext.Provider value={{ mode, changeMode, theme: themeStyle }}>
<Global <Global
styles={css` styles={css`
:root { :root {
${globalThemeConstant(theme)} ${globalThemeConstant(themeStyle)}
} }
`} `}
/> />
<EmotionThemeProvider theme={theme}>{children}</EmotionThemeProvider> <EmotionThemeProvider theme={themeStyle}>{children}</EmotionThemeProvider>
</ThemeContext.Provider> </ThemeContext.Provider>
); );
}; };
+3 -2
View File
@@ -1,7 +1,8 @@
export type ThemeMode = 'light' | 'dark'; export type Theme = 'light' | 'dark';
export type ThemeMode = Theme | 'auto';
export type ThemeProviderProps = { export type ThemeProviderProps = {
defaultThemeMode?: ThemeMode; defaultTheme?: Theme;
}; };
export type ThemeProviderValue = { export type ThemeProviderValue = {
+4 -2
View File
@@ -1,4 +1,4 @@
import { ThemeMode } from '@/styles/types'; import { Theme, ThemeMode } from '@/styles/types';
export class SystemTheme { export class SystemTheme {
media: MediaQueryList = window.matchMedia('(prefers-color-scheme: light)'); media: MediaQueryList = window.matchMedia('(prefers-color-scheme: light)');
@@ -11,7 +11,7 @@ export class SystemTheme {
this.media.addEventListener('change', this.eventHandler); this.media.addEventListener('change', this.eventHandler);
} }
get = (): ThemeMode => { get = (): Theme => {
if (typeof window === 'undefined') { if (typeof window === 'undefined') {
return 'light'; return 'light';
} }
@@ -37,3 +37,5 @@ export class LocalStorageThemeMode {
localStorage.setItem(this.name, mode); localStorage.setItem(this.name, mode);
}; };
} }
export const localStorageThemeMode = new LocalStorageThemeMode();