From e15668a10e7cfa297e8b46c20de38c2b19ff2e4c Mon Sep 17 00:00:00 2001 From: arvinxx Date: Mon, 9 Jan 2023 13:52:38 +0800 Subject: [PATCH] =?UTF-8?q?:sparkles:=20feat:=20=E4=B8=BA=20ThemeProvider?= =?UTF-8?q?=20=E5=A2=9E=E5=8A=A0=E4=B8=BB=E9=A2=98=E5=88=87=E6=8D=A2?= =?UTF-8?q?=E7=9A=84=E8=83=BD=E5=8A=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/containers/AppContainer/AntdProvider.tsx | 20 ---- src/containers/AppContainer/AppContainer.tsx | 17 ++-- src/containers/AppContainer/ThemeContent.tsx | 95 ------------------- src/containers/ThemeProvider/AntdProvider.tsx | 66 +++++++++++++ .../ThemeProvider/ThemeProvider.tsx | 21 ++++ .../ThemeProvider/TokenContainer.tsx | 58 +++++++++++ src/containers/ThemeProvider/index.tsx | 36 +------ src/containers/ThemeProvider/type.ts | 34 +++++++ tests/containers/AppContainer.test.tsx | 2 +- 9 files changed, 190 insertions(+), 159 deletions(-) delete mode 100644 src/containers/AppContainer/AntdProvider.tsx delete mode 100644 src/containers/AppContainer/ThemeContent.tsx create mode 100644 src/containers/ThemeProvider/AntdProvider.tsx create mode 100644 src/containers/ThemeProvider/ThemeProvider.tsx create mode 100644 src/containers/ThemeProvider/TokenContainer.tsx create mode 100644 src/containers/ThemeProvider/type.ts diff --git a/src/containers/AppContainer/AntdProvider.tsx b/src/containers/AppContainer/AntdProvider.tsx deleted file mode 100644 index 8e8db2a5..00000000 --- a/src/containers/AppContainer/AntdProvider.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { App, ConfigProvider } from 'antd'; -import { ConfigProviderProps } from 'antd/es/config-provider'; -import { type FC } from 'react'; - -import StaticMethod from './AntdStaticMethod'; - -export interface AntdProviderProps extends ConfigProviderProps { - className?: string; -} - -export const AntdProvider: FC = ({ children, className, ...props }) => { - return ( - - - - {children} - - - ); -}; diff --git a/src/containers/AppContainer/AppContainer.tsx b/src/containers/AppContainer/AppContainer.tsx index a7b64a75..2ccc0dfb 100644 --- a/src/containers/AppContainer/AppContainer.tsx +++ b/src/containers/AppContainer/AppContainer.tsx @@ -1,11 +1,12 @@ -import { memo, ReactElement, ReactNode } from 'react'; +import { App } from 'antd'; +import { memo, ReactElement } from 'react'; import { ThemeAppearance, ThemeMode } from '@/types'; -import ThemeContent, { ThemeContentProps } from './ThemeContent'; +import { ThemeProvider, ThemeProviderProps } from '../ThemeProvider'; import ThemeSwitcher from './ThemeSwitcher'; -export interface AppContainerProps> extends ThemeContentProps { +export interface AppContainerProps> extends ThemeProviderProps { /** * 应用的展示外观主题,只存在亮色和暗色两种 * @default light @@ -20,10 +21,7 @@ export interface AppContainerProps> extends ThemeC */ themeMode?: ThemeMode; - children: ReactNode; - className?: string; - prefixCls?: string; } export const AppContainer: (props: AppContainerProps) => ReactElement | null = memo( @@ -35,6 +33,7 @@ export const AppContainer: (props: AppContainerProps) => ReactElemen themeMode, customToken, customStylish, + className, ...props }) => ( (props: AppContainerProps) => ReactElemen appearance={appearance} onAppearanceChange={onAppearanceChange} > - - {children} - + + {children} + ), ); diff --git a/src/containers/AppContainer/ThemeContent.tsx b/src/containers/AppContainer/ThemeContent.tsx deleted file mode 100644 index 268aa673..00000000 --- a/src/containers/AppContainer/ThemeContent.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { GetAntdThemeConfig, GetCustomStylish, GetCustomToken } from '@/types'; -import { ReactElement, ReactNode, useMemo } from 'react'; - -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 interface ThemeContentProps> - extends Omit { - children: ReactNode; - /** - * 自定义 Token - */ - customToken?: T | GetCustomToken; - /** - * 自定义 Stylish - */ - customStylish?: S | GetCustomStylish; - /** - * 直接传入 antd 主题,或者传入一个函数,根据当前的主题模式返回对应的主题 - */ - theme?: ThemeConfig | GetAntdThemeConfig; -} - -const ThemeContent: (props: ThemeContentProps) => ReactElement | null = ({ - children, - customToken: customTokenOrFn, - customStylish: stylishOrGetStylish, - theme: themeProp, - ...props -}) => { - const { appearance, isDarkMode } = useThemeMode(); - const { stylish: antdStylish, ...token } = useAntdTheme(); - - // 获取 自定义 token - const customToken = useMemo(() => { - if (customTokenOrFn instanceof Function) { - return customTokenOrFn({ token, appearance, isDarkMode }); - } - - return customTokenOrFn; - }, [customTokenOrFn, token, appearance]); - - // 获取 stylish - const customStylish = useMemo(() => { - if (stylishOrGetStylish instanceof Function) { - return stylishOrGetStylish({ - token: { ...token, ...customToken }, - stylish: antdStylish, - appearance, - isDarkMode, - }); - } - return stylishOrGetStylish; - }, [stylishOrGetStylish, token, customToken, antdStylish, appearance]); - - const antdTheme = useMemo(() => { - const baseAlgorithm = isDarkMode ? theme.darkAlgorithm : theme.defaultAlgorithm; - - let antdTheme = themeProp as ThemeConfig | undefined; - - if (typeof themeProp === 'function') { - antdTheme = themeProp(appearance); - } - - if (!antdTheme) { - return { algorithm: baseAlgorithm }; - } - - // 如果有 themeProp 说明是外部传入的 theme,需要对算法做一个合并处理,因此先把 themeProp 的算法规整为一个数组 - const algoProp = !antdTheme.algorithm - ? [] - : antdTheme.algorithm instanceof Array - ? antdTheme.algorithm - : [antdTheme.algorithm]; - - return { - ...antdTheme, - algorithm: !antdTheme.algorithm ? baseAlgorithm : [baseAlgorithm, ...algoProp], - }; - }, [themeProp, isDarkMode]); - - return ( - - - {children} - - - ); -}; - -export default ThemeContent; diff --git a/src/containers/ThemeProvider/AntdProvider.tsx b/src/containers/ThemeProvider/AntdProvider.tsx new file mode 100644 index 00000000..280668ae --- /dev/null +++ b/src/containers/ThemeProvider/AntdProvider.tsx @@ -0,0 +1,66 @@ +import { ConfigProvider, message, Modal, notification, theme } from 'antd'; +import { memo, useEffect, useMemo, type FC } from 'react'; + +import { ThemeProviderProps } from '@/containers'; +import { useThemeMode } from '@/hooks'; +import { ThemeConfig } from 'antd/es/config-provider/context'; + +type AntdProviderProps = Pick< + ThemeProviderProps, + 'theme' | 'prefixCls' | 'getStaticInstance' | 'children' +>; + +const AntdProvider: FC = memo( + ({ children, theme: themeProp, prefixCls, getStaticInstance }) => { + const { appearance, isDarkMode } = useThemeMode(); + const [messageInstance, messageContextHolder] = message.useMessage(); + const [notificationInstance, notificationContextHolder] = notification.useNotification(); + const [modalInstance, modalContextHolder] = Modal.useModal(); + + useEffect(() => { + getStaticInstance?.({ + message: messageInstance, + modal: modalInstance, + notification: notificationInstance, + }); + }, []); + + // 获取 antd 主题 + const antdTheme = useMemo(() => { + const baseAlgorithm = isDarkMode ? theme.darkAlgorithm : theme.defaultAlgorithm; + + let antdTheme = themeProp as ThemeConfig | undefined; + + if (typeof themeProp === 'function') { + antdTheme = themeProp(appearance); + } + + if (!antdTheme) { + return { algorithm: baseAlgorithm }; + } + + // 如果有 themeProp 说明是外部传入的 theme,需要对算法做一个合并处理,因此先把 themeProp 的算法规整为一个数组 + const algoProp = !antdTheme.algorithm + ? [] + : antdTheme.algorithm instanceof Array + ? antdTheme.algorithm + : [antdTheme.algorithm]; + + return { + ...antdTheme, + algorithm: !antdTheme.algorithm ? baseAlgorithm : [baseAlgorithm, ...algoProp], + }; + }, [themeProp, isDarkMode]); + + return ( + + {messageContextHolder} + {notificationContextHolder} + {modalContextHolder} + {children} + + ); + }, +); + +export default AntdProvider; diff --git a/src/containers/ThemeProvider/ThemeProvider.tsx b/src/containers/ThemeProvider/ThemeProvider.tsx new file mode 100644 index 00000000..2a03cbde --- /dev/null +++ b/src/containers/ThemeProvider/ThemeProvider.tsx @@ -0,0 +1,21 @@ +import { ReactElement } from 'react'; + +import AntdProvider from './AntdProvider'; +import TokenContainer from './TokenContainer'; +import { ThemeProviderProps } from './type'; + +export const ThemeProvider: (props: ThemeProviderProps) => ReactElement | null = ({ + children, + customToken, + customStylish, + theme: themeProp, + prefixCls, +}) => { + return ( + + + {children} + + + ); +}; diff --git a/src/containers/ThemeProvider/TokenContainer.tsx b/src/containers/ThemeProvider/TokenContainer.tsx new file mode 100644 index 00000000..7048efb1 --- /dev/null +++ b/src/containers/ThemeProvider/TokenContainer.tsx @@ -0,0 +1,58 @@ +import { ThemeProvider as Provider } from '@emotion/react'; +import { ReactElement, useMemo } from 'react'; + +import { useThemeMode } from '@/hooks'; +import { useAntdTheme } from '@/hooks/useAntdTheme'; +import type { ThemeProviderProps } from './type'; + +import { Theme } from '@/types'; + +type TokenContainerProps> = Pick< + ThemeProviderProps, + 'children' | 'customToken' | 'customStylish' +>; + +const TokenContainer: (props: TokenContainerProps) => ReactElement | null = ({ + children, + customToken: customTokenOrFn, + customStylish: stylishOrGetStylish, +}) => { + const themeState = useThemeMode(); + const { appearance, isDarkMode } = themeState; + const { stylish: antdStylish, ...token } = useAntdTheme(); + + // 获取 自定义 token + const customToken = useMemo(() => { + if (customTokenOrFn instanceof Function) { + return customTokenOrFn({ token, appearance, isDarkMode }); + } + + return customTokenOrFn; + }, [customTokenOrFn, token, appearance]); + + // 获取 stylish + const customStylish = useMemo(() => { + if (stylishOrGetStylish instanceof Function) { + return stylishOrGetStylish({ + token: { ...token, ...customToken }, + stylish: antdStylish, + appearance, + isDarkMode, + }); + } + return stylishOrGetStylish; + }, [stylishOrGetStylish, token, customToken, antdStylish, appearance]); + + const stylish = { ...customStylish, ...antdStylish }; + + const theme: Theme = { + ...token, + ...customToken, + stylish, + ...themeState, + }; + + return {children}; +}; + +export default TokenContainer; diff --git a/src/containers/ThemeProvider/index.tsx b/src/containers/ThemeProvider/index.tsx index 13fce53a..91c60b93 100644 --- a/src/containers/ThemeProvider/index.tsx +++ b/src/containers/ThemeProvider/index.tsx @@ -1,34 +1,2 @@ -import { useAntdTheme, useThemeMode } from '@/hooks'; -import { Theme } from '@/types'; -import { ThemeProvider as Provider } from '@emotion/react'; -import { memo, PropsWithChildren, ReactElement } from 'react'; - -export interface ThemeProviderProps> { - /** - * 自定义 token, 可在 antd v5 token 规范基础上扩展和新增自己需要的 token - */ - customToken?: CT; - /** - * 自定义 stylish 可以自行扩展和新增自己需要的复合样式 - * @internal - */ - customStylish?: CS; -} - -export const ThemeProvider: , S = Record>( - props: PropsWithChildren>, -) => ReactElement | null = memo(({ children, customToken = {}, customStylish = {} }) => { - const { stylish: antdStylish, ...antdToken } = useAntdTheme(); - const themeState = useThemeMode(); - - const stylish = { ...customStylish, ...antdStylish }; - - const theme: Theme = { - ...antdToken, - ...customToken, - stylish, - ...themeState, - }; - - return {children}; -}); +export * from './ThemeProvider'; +export * from './type'; diff --git a/src/containers/ThemeProvider/type.ts b/src/containers/ThemeProvider/type.ts new file mode 100644 index 00000000..3cf4df9f --- /dev/null +++ b/src/containers/ThemeProvider/type.ts @@ -0,0 +1,34 @@ +import { GetAntdThemeConfig, GetCustomStylish, GetCustomToken } from '@/types'; +import { ThemeConfig } from 'antd/es/config-provider/context'; +import { MessageInstance } from 'antd/es/message/interface'; +import { ModalStaticFunctions } from 'antd/es/modal/confirm'; +import { NotificationInstance } from 'antd/es/notification/interface'; +import { ReactNode } from 'react'; + +export interface ThemeProviderProps> { + children: ReactNode; + /** + * 自定义 Token + */ + customToken?: T | GetCustomToken; + /** + * 自定义 Stylish + */ + customStylish?: S | GetCustomStylish; + /** + * 直接传入 antd 主题,或者传入一个函数,根据当前的主题模式返回对应的主题 + */ + theme?: ThemeConfig | GetAntdThemeConfig; + prefixCls?: string; + /** + * 从 ThemeProvider 中获取静态方法的实例对象 + * @param instances + */ + getStaticInstance?: (instances: StaticInstance) => void; +} + +export interface StaticInstance { + message: MessageInstance; + notification: NotificationInstance; + modal: Omit; +} diff --git a/tests/containers/AppContainer.test.tsx b/tests/containers/AppContainer.test.tsx index 218d8e8a..829e4560 100644 --- a/tests/containers/AppContainer.test.tsx +++ b/tests/containers/AppContainer.test.tsx @@ -134,7 +134,7 @@ describe('AppContainer', () => { const { result: dark } = renderHook(useTheme, { wrapper: Darker }); expect(dark.current.customColor).toEqual('#000'); - expect(dark.current.customBrandColor).toEqual('#1677ff'); + expect(dark.current.customBrandColor).toEqual('#1668dc'); }); it('注入自定义 stylish', () => {