From fb828c9cf4304b857798d3d0dd4aedf125342e53 Mon Sep 17 00:00:00 2001 From: QiShaoXuan Date: Tue, 27 Sep 2022 17:19:23 +0800 Subject: [PATCH] feat: add system theme judgment --- src/styles/themeProvider.tsx | 39 +++++++++++++++++++++++++++++++++--- src/styles/types.ts | 2 +- src/styles/utils.ts | 39 ++++++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 4 deletions(-) create mode 100644 src/styles/utils.ts diff --git a/src/styles/themeProvider.tsx b/src/styles/themeProvider.tsx index 383ae2dd74..27f94ca682 100644 --- a/src/styles/themeProvider.tsx +++ b/src/styles/themeProvider.tsx @@ -3,10 +3,11 @@ import { Global, css, } from '@emotion/react'; -import { createContext, useState } from 'react'; +import { createContext, useEffect, useRef, useState } from 'react'; import type { PropsWithChildren } from 'react'; import { ThemeMode, ThemeProviderProps, ThemeProviderValue } from './types'; import { lightTheme, darkTheme, globalThemeConstant } from './theme'; +import { SystemTheme, LocalStorageThemeMode } from '@/styles/utils'; export const ThemeContext = createContext({ mode: 'light', @@ -15,15 +16,47 @@ export const ThemeContext = createContext({ }); export const ThemeProvider = ({ - defaultMode = 'light', + defaultThemeMode = 'light', children, }: PropsWithChildren) => { - const [mode, setMode] = useState(defaultMode); + const { current: localStorageThemeMode } = useRef( + new LocalStorageThemeMode() + ); + + const [mode, setMode] = useState(defaultThemeMode); const theme = mode === 'dark' ? darkTheme : lightTheme; + const changeMode = (themeMode: ThemeMode) => { setMode(themeMode); + // Remember the theme mode which user selected for next time + localStorageThemeMode.set(themeMode); }; + useEffect(() => { + 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(); + if (selectedThemeMode) { + defaultThemeMode !== selectedThemeMode && setMode(selectedThemeMode); + } else { + // If user has not selected a theme mode, use system theme + const systemThemeMode = systemTheme.get(); + defaultThemeMode !== systemThemeMode && setMode(systemThemeMode); + } + + // When system theme changed, change the theme mode + systemTheme.onChange(() => { + // TODO: There may be should be provided a way to let user choose whether to + setMode(systemTheme.get()); + }); + + return () => { + systemTheme.dispose(); + }; + }, []); + return ( void> = []; + eventHandler = (e: Event) => { + this.eventList.forEach(fn => fn(e)); + }; + + constructor() { + this.media.addEventListener('change', this.eventHandler); + } + + get = (): ThemeMode => { + if (typeof window === 'undefined') { + return 'light'; + } + return this.media.matches ? 'light' : 'dark'; + }; + + onChange = (callback: () => void) => { + this.eventList.push(callback); + }; + + dispose = () => { + this.eventList = []; + this.media.removeEventListener('change', this.eventHandler); + }; +} + +export class LocalStorageThemeMode { + name = 'Affine-theme-mode'; + get = (): ThemeMode | null => { + return localStorage.getItem(this.name) as ThemeMode | null; + }; + set = (mode: ThemeMode) => { + localStorage.setItem(this.name, mode); + }; +}