diff --git a/.dumi/theme/SiteThemeProvider.tsx b/.dumi/theme/SiteThemeProvider.tsx index dca640da9..309daabbf 100644 --- a/.dumi/theme/SiteThemeProvider.tsx +++ b/.dumi/theme/SiteThemeProvider.tsx @@ -12,9 +12,7 @@ import useSiteToken from '../hooks/useSiteToken'; const SiteThemeProvider: FC< ThemeProviderProps & { - theme: ThemeConfig & { - isDark?: boolean; - }; + theme: ThemeConfig; } > = ({ children, theme, ...rest }) => { const { getPrefixCls, iconPrefixCls } = useContext(ConfigProvider.ConfigContext); diff --git a/.dumi/theme/common/ThemeSwitch/index.tsx b/.dumi/theme/common/ThemeSwitch/index.tsx index 68b70e956..b9f61e48f 100644 --- a/.dumi/theme/common/ThemeSwitch/index.tsx +++ b/.dumi/theme/common/ThemeSwitch/index.tsx @@ -3,8 +3,9 @@ import { CompactTheme, DarkTheme, Motion } from 'antd-token-previewer/es/icons'; import { FormattedMessage } from 'dumi'; import React from 'react'; import ThemeIcon from './ThemeIcon'; +import { FontColorsOutlined } from '@ant-design/icons'; -export type ThemeName = 'light' | 'dark' | 'compact' | 'motion-off'; +export type ThemeName = 'light' | 'dark' | 'compact' | 'motion-off' | 'custom-font'; export type ThemeSwitchProps = { value?: ThemeName[]; @@ -14,6 +15,7 @@ export type ThemeSwitchProps = { const ThemeSwitch: React.FC = (props: ThemeSwitchProps) => { const { value = ['light'], onChange } = props; const isMotionOff = value.includes('motion-off'); + const isCustomFont = value.includes('custom-font'); return ( }> @@ -57,6 +59,22 @@ const ThemeSwitch: React.FC = (props: ThemeSwitchProps) => { /> } /> + } + type={isCustomFont ? 'primary' : 'default'} + onClick={() => { + if (isCustomFont) { + onChange(value.filter(theme => theme !== 'custom-font')); + } else { + onChange([...value, 'custom-font']); + } + }} + tooltip={ + + } + /> ); }; diff --git a/.dumi/theme/layouts/GlobalLayout.tsx b/.dumi/theme/layouts/GlobalLayout.tsx index faca35068..a89beb68b 100644 --- a/.dumi/theme/layouts/GlobalLayout.tsx +++ b/.dumi/theme/layouts/GlobalLayout.tsx @@ -124,6 +124,7 @@ const GlobalLayout: React.FC = () => { theme={{ algorithm: getAlgorithm(theme), isDark: theme.includes('dark'), + customFont: theme.includes('custom-font'), token: { motion: !theme.includes('motion-off'), }, diff --git a/.dumi/theme/locales/en-US.json b/.dumi/theme/locales/en-US.json index 69089991f..cc0fcfc90 100644 --- a/.dumi/theme/locales/en-US.json +++ b/.dumi/theme/locales/en-US.json @@ -3,6 +3,8 @@ "app.theme.switch.compact": "Compact theme", "app.theme.switch.motion.on": "Motion On", "app.theme.switch.motion.off": "Motion Off", + "app.theme.switch.font.default": "Default Font", + "app.theme.switch.font.custom": "Custom Font", "app.header.menu.more": "More", "app.header.menu.pro.components": "Ant Design Pro Components", "app.header.menu.charts": "Ant Design Charts", diff --git a/.dumi/theme/locales/zh-CN.json b/.dumi/theme/locales/zh-CN.json index b70ec6a11..f943c7c2f 100644 --- a/.dumi/theme/locales/zh-CN.json +++ b/.dumi/theme/locales/zh-CN.json @@ -3,6 +3,8 @@ "app.theme.switch.compact": "紧凑主题", "app.theme.switch.motion.on": "动画开启", "app.theme.switch.motion.off": "动画关闭", + "app.theme.switch.font.default": "默认字体", + "app.theme.switch.font.custom": "定制字体", "app.header.menu.more": "更多", "app.header.menu.pro.components": "Ant Design Pro Components", "app.header.menu.charts": "Ant Design Charts", diff --git a/packages/design/src/_util/genComponentStyleHook.ts b/packages/design/src/_util/genComponentStyleHook.ts index f32a1c847..ae4359d47 100644 --- a/packages/design/src/_util/genComponentStyleHook.ts +++ b/packages/design/src/_util/genComponentStyleHook.ts @@ -1,14 +1,29 @@ import type { CSSObject } from '@ant-design/cssinjs'; import type { ComponentTokenMap } from 'antd/es/theme/interface'; -import type { FullToken, GenerateStyle } from 'antd/es/theme/internal'; +import type { DerivativeToken, FullToken, GenerateStyle } from 'antd/es/theme/internal'; import { genComponentStyleHook as antGenComponentStyleHook } from 'antd/es/theme/internal'; import type { GlobalToken } from 'antd/es/theme/interface'; import type { OverrideTokenWithoutDerivative } from 'antd/es/theme/util/genComponentStyleHook'; import { useContext } from 'react'; +import ConfigProvider from '../config-provider'; import theme from '../theme'; export type ComponentName = keyof ComponentTokenMap; +export const genCustomFontStyle = (token: DerivativeToken): CSSObject[] => { + return [ + { + ['@font-face']: { + fontFamily: 'Source Sans Pro', + // 定义三种字体格式,适配不同版本的浏览器,并且最多加载一种字体文件 + src: `url('https://mdn.alipayobjects.com/huamei_fhnyvh/afts/file/A*H1MFR42M5PMAAAAAAAAAAAAADmfOAQ/Source%20Sans%20Pro.woff2') format('woff2'), url('https://mdn.alipayobjects.com/huamei_fhnyvh/afts/file/A*jbYLSpw_gfEAAAAAAAAAAAAADmfOAQ/Source%20Sans%20Pro.woff') format('woff'), url('https://mdn.alipayobjects.com/huamei_fhnyvh/afts/file/A*28ClS5qHwQ8AAAAAAAAAAAAADmfOAQ/Source%20Sans%20Pro.ttf') format('truetype')`, + // 定义字体加载策略,外置字体加载前使用默认字体进行兜底 + fontDisplay: 'swap', + }, + }, + ]; +}; + export function genComponentStyleHook( componentName: ComponentName, styleFn: GenerateStyle>, @@ -17,10 +32,11 @@ export function genComponentStyleHook( | ((token: GlobalToken) => OverrideTokenWithoutDerivative[ComponentName]) ) { return (prefixCls: string) => { + const { theme: themeConfig } = useContext(ConfigProvider.ConfigContext); const useStyle = antGenComponentStyleHook( `OB-${componentName}` as ComponentName, token => { - return [styleFn(token)]; + return [themeConfig?.customFont ? genCustomFontStyle(token) : null, styleFn(token)]; }, getDefaultToken, { diff --git a/packages/design/src/config-provider/__tests__/theme.test.tsx b/packages/design/src/config-provider/__tests__/theme.test.tsx index 3bb0a2722..021cb6b1f 100644 --- a/packages/design/src/config-provider/__tests__/theme.test.tsx +++ b/packages/design/src/config-provider/__tests__/theme.test.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useContext } from 'react'; import { render } from '@testing-library/react'; import { ConfigProvider, useToken } from '@oceanbase/design'; import defaultTheme from '../../theme/default'; diff --git a/packages/design/src/config-provider/index.md b/packages/design/src/config-provider/index.md index 074c5c5ed..b31a1c600 100644 --- a/packages/design/src/config-provider/index.md +++ b/packages/design/src/config-provider/index.md @@ -8,6 +8,7 @@ nav: - 🔥 完全继承 antd [ConfigProvider](https://ant.design/components/config-provider-cn) 的能力和 API,可无缝切换。 - 🌈 定制全局主题和空状态,以符合 OceanBase Design 设计规范。 - 🆕 默认内嵌 [App 包裹组件](https://ant.design/components/app-cn),支持 message, notification 和 Modal 等静态方法消费 ConfigProvider 配置。 +- 🆕 新增 `theme.customFont` 属性,用于开启 `Source Sans Pro` 定制字体以提升展示效果,仅支持线上应用和英文环境。 - 🆕 新增 `table.selectionColumnWidth` 属性,用于配置表格的展开列宽度。 - 🆕 新增 `injectStaticFunction` 属性,用于配置 `message`, `notification` 和 `Modal` 静态方法是否可以消费全局配置,默认开启。 @@ -47,6 +48,7 @@ export default App; | 参数 | 说明 | 类型 | 默认值 | 版本 | | :-- | :-- | :-- | :-- | :-- | +| theme.customFont | 用于开启 `Source Sans Pro` 定制字体以提升展示效果,仅支持线上应用和英文环境 | boolean | undefined | 0.3.1 | | spin | Spin 全局配置 | `{ indicator?: ReactNode; className?: string; style?: React.CSSProperties; }` | undefined | - | | table | Table 全局配置 | `{ selectionColumnWidth?: width; className?: string; style?: React.CSSProperties; }` | undefined | - | | injectStaticFunction | 用于配置 `message`, `notification` 和 `Modal` 静态方法是否可以消费全局配置 | boolean | true | - | diff --git a/packages/design/src/config-provider/index.tsx b/packages/design/src/config-provider/index.tsx index 3acceeed7..babcf1c1e 100644 --- a/packages/design/src/config-provider/index.tsx +++ b/packages/design/src/config-provider/index.tsx @@ -28,6 +28,8 @@ export * from 'antd/es/config-provider'; export interface ThemeConfig extends AntThemeConfig { isDark?: boolean; + /* use custom font or not */ + customFont?: boolean; } export type SpinConfig = ComponentStyleConfig & { @@ -73,8 +75,6 @@ const ExtendedConfigContext = React.createContext({ hideOnSinglePage: false, }); -const { defaultSeed } = themeConfig; - export type ConfigProviderType = React.FC & { ExtendedConfigContext: typeof ExtendedConfigContext; } & { @@ -103,6 +103,7 @@ const ConfigProvider: ConfigProviderType = ({ React.useContext(ExtendedConfigContext); const mergedTheme = merge(parentContext.theme, theme); const currentTheme = mergedTheme?.isDark ? darkTheme : defaultTheme; + const { token } = themeConfig.useToken(); // inherit from parent StyleProvider const parentStyleContext = React.useContext(StyleContext); @@ -126,16 +127,13 @@ const ConfigProvider: ConfigProviderType = ({ parentContext.tabs, tabs )} - theme={merge( - { - token: { - ...defaultSeed, - ...currentTheme.token, - }, - components: currentTheme.components, + theme={merge(currentTheme, mergedTheme, { + token: { + fontFamily: mergedTheme.customFont + ? `'Source Sans Pro', ${token.fontFamily}` + : token.fontFamily, }, - mergedTheme - )} + })} renderEmpty={ parentContext.renderEmpty || (componentName => ) diff --git a/packages/design/src/index.ts b/packages/design/src/index.ts index fc30708dd..c2ad7765b 100644 --- a/packages/design/src/index.ts +++ b/packages/design/src/index.ts @@ -24,7 +24,7 @@ export { default as Card } from './card'; export type { CardProps } from './card'; export { default as ConfigProvider } from './config-provider'; -export type { ConfigProviderProps } from './config-provider'; +export type { ConfigProviderProps, ConfigConsumerProps, ThemeConfig } from './config-provider'; export { default as Descriptions } from './descriptions'; export type {