Skip to content

Commit

Permalink
✨ feat: useThemeMode 支持获取当前浏览器主题模式
Browse files Browse the repository at this point in the history
  • Loading branch information
arvinxx committed Mar 14, 2023
1 parent 69b7396 commit a4e2e67
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 6 deletions.
6 changes: 5 additions & 1 deletion src/context/ThemeModeContext.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { createContext } from 'react';

import { ThemeContextState } from '@/types';
import { ThemeAppearance, ThemeContextState } from '@/types';

const matchThemeMode = (mode: ThemeAppearance) =>
matchMedia && matchMedia(`(prefers-color-scheme: ${mode})`);

export const ThemeModeContext = createContext<ThemeContextState>({
appearance: 'light',
isDarkMode: false,
themeMode: 'light',
browserPrefers: matchThemeMode('dark')?.matches ? 'dark' : 'light',
});
38 changes: 35 additions & 3 deletions src/factories/createThemeProvider/ThemeSwitcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useMergeValue } from '@/utils/useMergeValue';
import { FC, memo, ReactNode, useEffect, useLayoutEffect, useMemo, useState } from 'react';

import { ThemeModeContext } from '@/context';
import { ThemeAppearance, ThemeMode, UseTheme } from '@/types';
import { BrowserPrefers, ThemeAppearance, ThemeMode, UseTheme } from '@/types';

let darkThemeMatch: MediaQueryList;

Expand All @@ -12,7 +12,8 @@ const matchThemeMode = (mode: ThemeAppearance) =>
const ThemeObserver: FC<{
themeMode: ThemeMode;
setAppearance: (value: ThemeAppearance) => void;
}> = ({ themeMode, setAppearance }) => {
setBrowserPrefers: (value: BrowserPrefers) => void;
}> = ({ themeMode, setAppearance, setBrowserPrefers }) => {
const matchBrowserTheme = () => {
if (matchThemeMode('dark').matches) {
setAppearance('dark');
Expand All @@ -21,6 +22,14 @@ const ThemeObserver: FC<{
}
};

const updateBrowserTheme = () => {
if (matchThemeMode('dark').matches) {
setBrowserPrefers('dark');
} else {
setBrowserPrefers('light');
}
};

// 自动监听系统主题变更
useLayoutEffect(() => {
// 如果不是自动,就明确设定亮暗色
Expand All @@ -41,6 +50,18 @@ const ThemeObserver: FC<{
};
}, [themeMode]);

useEffect(() => {
if (!darkThemeMatch) {
darkThemeMatch = matchThemeMode('dark');
}

darkThemeMatch.addEventListener('change', updateBrowserTheme);

return () => {
darkThemeMatch.removeEventListener('change', updateBrowserTheme);
};
}, []);

return null;
};

Expand Down Expand Up @@ -85,6 +106,10 @@ const ThemeSwitcher: FC<ThemeSwitcherProps> = memo(
onChange: onAppearanceChange,
});

const [browserPrefers, setBrowserPrefers] = useState<BrowserPrefers>(
matchThemeMode('dark')?.matches ? 'dark' : 'light',
);

const [startObserver, setStartObserver] = useState(false);

// Wait until after client-side hydration to show
Expand All @@ -98,9 +123,16 @@ const ThemeSwitcher: FC<ThemeSwitcherProps> = memo(
themeMode,
appearance,
isDarkMode: appearance === 'dark',
browserPrefers,
}}
>
{startObserver && <ThemeObserver themeMode={themeMode} setAppearance={setAppearance} />}
{startObserver && (
<ThemeObserver
themeMode={themeMode}
setAppearance={setAppearance}
setBrowserPrefers={setBrowserPrefers}
/>
)}
{children}
</ThemeModeContext.Provider>
);
Expand Down
19 changes: 19 additions & 0 deletions src/factories/createThemeProvider/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,27 @@ export interface ThemeProviderProps<T, S = Record<string, string>> {
themeMode?: ThemeMode;
}

/**
* 静态实例
*/
export interface StaticInstance {
/**
* 消息实例
*/
message: MessageInstance;
/**
* 通知实例
*/
notification: NotificationInstance;
/**
* 弹窗实例,不包含 warn 方法
* @typedef {object} Omit<ModalStaticFunctions, 'warn'>
* @property {Function} info - info 弹窗
* @property {Function} success - success 弹窗
* @property {Function} error - error 弹窗
* @property {Function} warning - warning 弹窗
* @property {Function} confirm - confirm 弹窗
* @property {Function} destroyAll - 关闭所有弹窗
*/
modal: Omit<ModalStaticFunctions, 'warn'>;
}
2 changes: 2 additions & 0 deletions src/types/appearance.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export type BrowserPrefers = 'dark' | 'light';

export type ThemeAppearance = 'dark' | 'light' | string;

export type ThemeMode = 'auto' | 'dark' | 'light';
29 changes: 27 additions & 2 deletions src/types/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,34 @@ import { ThemeConfig } from 'antd';
import { MappingAlgorithm } from 'antd/es/config-provider/context';
import { AliasToken } from 'antd/es/theme/interface';

import { ThemeAppearance, ThemeMode } from './appearance';
import { BrowserPrefers, ThemeAppearance, ThemeMode } from './appearance';

/**
* @title 主题上下文状态
*/
export interface ThemeContextState {
/**
* @title 外观
*/
appearance: ThemeAppearance;
/**
* @title 主题模式
* @enum ["light", "dark"]
* @enumNames ["亮色模式", "暗色模式"]
* @default "light"
*/
themeMode: ThemeMode;
/**
* @title 是否为暗色模式
*/
isDarkMode: boolean;
/**
* @title 浏览器偏好的外观
*/
browserPrefers: BrowserPrefers;
}

export type AppearanceState = Omit<ThemeContextState, 'themeMode'>;
export type AppearanceState = Omit<ThemeContextState, 'themeMode' | 'browserPrefers'>;

export type AntdToken = AliasToken;

Expand All @@ -21,9 +40,15 @@ export interface AntdStylish {
buttonDefaultHover: string;
}

/**
* @title 获取 Antd 主题的函数
* @param appearance - 主题外观
* @returns Antd 主题配置对象或 undefined
*/
export interface GetAntdTheme {
(appearance: ThemeAppearance): ThemeConfig | undefined;
}

export type { MappingAlgorithm };

// eslint-disable-next-line @typescript-eslint/no-empty-interface
Expand Down

0 comments on commit a4e2e67

Please sign in to comment.