generated from arvinxx/npm-template
-
Notifications
You must be signed in to change notification settings - Fork 35
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
211 additions
and
71 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
import { ReactElement, ReactNode, useMemo } from 'react'; | ||
|
||
import { AntdStylish, AntdToken, FullToken, ThemeAppearance } from '@/types'; | ||
|
||
import { useThemeMode } from '@/hooks'; | ||
import { useAntdTheme } from '@/hooks/useAntdTheme'; | ||
import { theme } from 'antd'; | ||
import { ThemeConfig } from 'antd/es/config-provider/context'; | ||
import { ThemeProvider } from '../ThemeProvider'; | ||
import { AntdProvider, type AntdProviderProps } from './AntdProvider'; | ||
|
||
export type GetCustomToken<T> = (theme: { token: AntdToken; appearance: ThemeAppearance }) => T; | ||
|
||
export type GetCustomStylish<S> = (theme: { | ||
token: FullToken; | ||
stylish: AntdStylish; | ||
appearance: ThemeAppearance; | ||
}) => S; | ||
|
||
export interface ThemeContentProps<T, S = Record<string, string>> extends AntdProviderProps { | ||
children: ReactNode; | ||
/** | ||
* 自定义 Token | ||
*/ | ||
customToken?: T | GetCustomToken<T>; | ||
/** | ||
* 自定义 Stylish | ||
*/ | ||
customStylish?: S | GetCustomStylish<S>; | ||
} | ||
|
||
const ThemeContent: <T, S>(props: ThemeContentProps<T, S>) => ReactElement | null = ({ | ||
children, | ||
customToken: customTokenOrFn, | ||
customStylish: stylishOrGetStylish, | ||
theme: themeProp, | ||
...props | ||
}) => { | ||
const { appearance, isDarkMode } = useThemeMode(); | ||
const { stylish: antdStylish, ...token } = useAntdTheme(); | ||
|
||
// 获取 自定义 token | ||
const customToken = useMemo(() => { | ||
if (typeof customTokenOrFn === 'function') { | ||
// @ts-ignore | ||
return customTokenOrFn({ token, appearance }); | ||
} | ||
|
||
return customTokenOrFn; | ||
}, [customTokenOrFn, token, appearance]); | ||
|
||
// 获取 stylish | ||
const customStylish = useMemo(() => { | ||
if (typeof stylishOrGetStylish === 'function') { | ||
// @ts-ignore | ||
return stylishOrGetStylish({ token: { ...token, ...customToken }, stylish: antdStylish }); | ||
} | ||
return stylishOrGetStylish; | ||
}, [stylishOrGetStylish, token, customToken, antdStylish, appearance]); | ||
|
||
const antdTheme = useMemo<ThemeConfig>(() => { | ||
const baseAlgorithm = isDarkMode ? theme.darkAlgorithm : theme.defaultAlgorithm; | ||
|
||
if (!themeProp) { | ||
return { algorithm: baseAlgorithm }; | ||
} | ||
|
||
// 如果有 themeProp 说明是外部传入的 theme,需要对算法做一个合并处理,因此先把 themeProp 的算法规整为一个数组 | ||
const algoProp = !themeProp.algorithm | ||
? [] | ||
: themeProp.algorithm instanceof Array | ||
? themeProp.algorithm | ||
: [themeProp.algorithm]; | ||
|
||
return { | ||
...themeProp, | ||
algorithm: !themeProp.algorithm ? baseAlgorithm : [baseAlgorithm, ...algoProp], | ||
}; | ||
}, [themeProp, isDarkMode]); | ||
|
||
return ( | ||
<AntdProvider theme={antdTheme} {...props}> | ||
<ThemeProvider customToken={customToken} customStylish={customStylish}> | ||
{children} | ||
</ThemeProvider> | ||
</AntdProvider> | ||
); | ||
}; | ||
|
||
export default ThemeContent; |
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,90 @@ | ||
import { FC, memo, ReactNode, useCallback, useLayoutEffect } from 'react'; | ||
import useControlledState from 'use-merge-value'; | ||
|
||
import { ThemeModeContext } from '@/context'; | ||
import { ThemeAppearance, ThemeMode } from '@/types'; | ||
|
||
let darkThemeMatch: MediaQueryList; | ||
|
||
const matchThemeMode = (mode: ThemeAppearance) => matchMedia(`(prefers-color-scheme: ${mode})`); | ||
|
||
export interface ThemeSwitcherProps { | ||
/** | ||
* 应用的展示外观主题,只存在亮色和暗色两种 | ||
* @default light | ||
*/ | ||
appearance?: ThemeAppearance; | ||
defaultAppearance?: ThemeAppearance; | ||
onAppearanceChange?: (mode: ThemeAppearance) => void; | ||
/** | ||
* 主题的展示模式,有三种配置:跟随系统、亮色、暗色 | ||
* 默认不开启自动模式,需要手动进行配置 | ||
* @default light | ||
*/ | ||
themeMode?: ThemeMode; | ||
|
||
children: ReactNode; | ||
} | ||
|
||
const ThemeSwitcher: FC<ThemeSwitcherProps> = memo( | ||
({ | ||
children, | ||
appearance: appearanceProp, | ||
defaultAppearance, | ||
onAppearanceChange, | ||
themeMode = 'light', | ||
}) => { | ||
const [appearance, setAppearance] = useControlledState<ThemeAppearance>('light', { | ||
value: appearanceProp, | ||
defaultValue: defaultAppearance, | ||
onChange: onAppearanceChange, | ||
}); | ||
|
||
const matchBrowserTheme = useCallback(() => { | ||
if (matchThemeMode('dark').matches) { | ||
setAppearance('dark'); | ||
} else { | ||
setAppearance('light'); | ||
} | ||
}, [setAppearance]); | ||
|
||
useLayoutEffect(() => { | ||
// 如果是自动的话,则去做一次匹配 | ||
if (themeMode === 'auto') matchBrowserTheme(); | ||
// 否则就明确设定亮暗色 | ||
else setAppearance(themeMode); | ||
}, [themeMode]); | ||
|
||
useLayoutEffect(() => { | ||
if (!darkThemeMatch) { | ||
darkThemeMatch = matchThemeMode('dark'); | ||
} | ||
|
||
if (!themeMode || themeMode === 'auto') { | ||
setTimeout(() => { | ||
matchBrowserTheme(); | ||
}, 100); | ||
} | ||
|
||
darkThemeMatch.addEventListener('change', matchBrowserTheme); | ||
|
||
return () => { | ||
darkThemeMatch.removeEventListener('change', matchBrowserTheme); | ||
}; | ||
}, []); | ||
|
||
return ( | ||
<ThemeModeContext.Provider | ||
value={{ | ||
themeMode, | ||
appearance, | ||
isDarkMode: appearance === 'dark', | ||
}} | ||
> | ||
{children} | ||
</ThemeModeContext.Provider> | ||
); | ||
}, | ||
); | ||
|
||
export default ThemeSwitcher; |
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,3 +1,3 @@ | ||
export type DisplayTheme = 'dark' | 'light'; | ||
export type ThemeAppearance = 'dark' | 'light'; | ||
|
||
export type ThemeMode = 'auto' | 'dark' | 'light'; |