@arcblock/ux 2.13.21 → 2.13.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,7 +3,6 @@
3
3
 
4
4
  import { createTheme as _createTheme, responsiveFontSizes } from '@mui/material/styles';
5
5
  import { deepmerge } from '@mui/utils';
6
- import pick from 'lodash/pick';
7
6
  import webfontloader from 'webfontloader';
8
7
  import { BLOCKLET_THEME_LIGHT, BLOCKLET_THEME_DARK, DEFAULT_FONTS, BLOCKLET_THEME_PREFER_KEY } from '@blocklet/theme';
9
8
  import { cleanedObj, deepmergeAll } from '../Util';
@@ -16,6 +15,11 @@ import '@fontsource/roboto/latin-ext-400.css';
16
15
  import '@fontsource/roboto/latin-ext-500.css';
17
16
  import '@fontsource/roboto/latin-ext-700.css';
18
17
 
18
+ /** 是否是 MUI Theme 对象 */
19
+ export function isTheme(obj) {
20
+ return obj && typeof obj === 'object' && obj.palette && typeof obj.palette.getContrastText === 'function';
21
+ }
22
+
19
23
  // 收集字体配置
20
24
  export function collectFontFamilies(obj, fontSet = new Set()) {
21
25
  if (!obj || typeof obj !== 'object') return fontSet;
@@ -90,15 +94,12 @@ export function createDefaultThemeOptions(mode = 'light') {
90
94
  }
91
95
  return BLOCKLET_THEME_LIGHT;
92
96
  }
93
-
94
97
  // 用于获取 Blocklet Theme 配置,便于用户创建自定义主题
95
-
96
98
  export function lazyThemeConfig(mode) {
97
- const fields = ['palette'];
98
99
  let config = null;
99
100
  return () => {
100
101
  if (config) return config;
101
- config = deepmerge(pick(createDefaultThemeOptions(mode), fields), pick(window.blocklet?.theme?.[mode] ?? {}, fields));
102
+ config = deepmerge(createDefaultThemeOptions(mode), window.blocklet?.theme?.[mode] ?? {});
102
103
  return config;
103
104
  };
104
105
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcblock/ux",
3
- "version": "2.13.21",
3
+ "version": "2.13.24",
4
4
  "description": "Common used react components for arcblock products",
5
5
  "keywords": [
6
6
  "react",
@@ -71,14 +71,14 @@
71
71
  "react": ">=18.2.0",
72
72
  "react-router-dom": ">=6.22.3"
73
73
  },
74
- "gitHead": "ff25dbcf651ad7e8c0b940525e285f2cebe3e52d",
74
+ "gitHead": "57fa9683f81c72474528412d1a2205dab186f4f9",
75
75
  "dependencies": {
76
76
  "@arcblock/did-motif": "^1.1.13",
77
- "@arcblock/icons": "^2.13.21",
78
- "@arcblock/nft-display": "^2.13.21",
79
- "@arcblock/react-hooks": "^2.13.21",
77
+ "@arcblock/icons": "^2.13.24",
78
+ "@arcblock/nft-display": "^2.13.24",
79
+ "@arcblock/react-hooks": "^2.13.24",
80
80
  "@babel/plugin-syntax-dynamic-import": "^7.8.3",
81
- "@blocklet/theme": "^2.13.21",
81
+ "@blocklet/theme": "^2.13.24",
82
82
  "@fontsource/roboto": "~5.1.1",
83
83
  "@fontsource/ubuntu-mono": "^5.0.18",
84
84
  "@iconify-icons/logos": "^1.2.36",
@@ -1,49 +1,10 @@
1
- import { createContext, useContext, ReactNode, useMemo, useState, useCallback, useEffect } from 'react';
2
- import { ThemeOptions, type PaletteMode } from '@mui/material';
3
- import { BLOCKLET_THEME_PREFER_KEY } from '@blocklet/theme';
4
- import set from 'lodash/set';
5
- import { ThemeProvider as EmotionThemeProvider } from '@emotion/react';
1
+ import { type ReactNode } from 'react';
6
2
  import { LocaleProvider, LocaleProviderProps, useLocaleContext } from '../Locale/context';
7
- import ThemeProvider, { ThemeProviderProps } from '../Theme/theme-provider';
8
- import {
9
- createTheme,
10
- getDefaultThemePrefer,
11
- lazyThemeConfig,
12
- useTheme,
13
- type UserThemeOptions,
14
- type ThemeConfig,
15
- } from '../Theme';
3
+ import ThemeProvider, { type ThemeProviderProps, useColorScheme } from '../Theme/theme-provider';
4
+ import { useTheme } from '../Theme';
16
5
 
17
- type Prefer = 'light' | 'dark' | 'system';
18
-
19
- export interface ConfigContextType {
20
- mode: PaletteMode;
21
- prefer?: Prefer;
22
- themeOptions: UserThemeOptions;
23
- toggleMode: () => void;
24
- }
25
-
26
- const resolveMode = (prefer?: Prefer): PaletteMode => {
27
- if (prefer) {
28
- if (prefer === 'system') {
29
- // 取系统默认
30
- return getDefaultThemePrefer({ theme: { prefer: 'system' } });
31
- }
32
- return prefer;
33
- }
34
-
35
- return getDefaultThemePrefer();
36
- };
37
-
38
- const ConfigContext = createContext<ConfigContextType>({} as ConfigContextType);
39
-
40
- export interface ConfigProviderProps
41
- extends Omit<LocaleProviderProps, 'translations'>,
42
- Omit<ThemeProviderProps, 'theme'> {
6
+ export interface ConfigProviderProps extends Omit<LocaleProviderProps, 'translations'>, ThemeProviderProps {
43
7
  children: ReactNode;
44
- prefer?: Prefer;
45
- theme?: UserThemeOptions | ((config: ThemeConfig) => UserThemeOptions);
46
- disableBlockletTheme?: boolean;
47
8
  translations?: Record<string, any>;
48
9
  }
49
10
 
@@ -52,91 +13,48 @@ export interface ConfigProviderProps
52
13
  */
53
14
  export function ConfigProvider({
54
15
  children,
55
- // theme config
16
+ // theme provider
17
+ theme,
18
+ injectFirst,
19
+ darkSchemeClass,
56
20
  prefer,
57
- theme: themeOptions,
58
21
  disableBlockletTheme = false,
59
- injectFirst,
60
- // locale config
22
+ enableColorScheme,
23
+ // locale provider
61
24
  locale,
62
25
  fallbackLocale,
63
26
  translations = {},
64
27
  languages,
65
28
  onLoadingTranslation,
66
29
  }: ConfigProviderProps) {
67
- const [mode, setMode] = useState<PaletteMode>(() => resolveMode(prefer));
68
-
69
- const _themeOptions = useMemo(() => {
70
- let result: ThemeOptions = {};
71
- const getThemeConfig = lazyThemeConfig(mode);
72
-
73
- if (themeOptions) {
74
- if (typeof themeOptions === 'function') {
75
- result = themeOptions(getThemeConfig());
76
- } else {
77
- result = { ...themeOptions };
78
- }
79
- }
80
-
81
- set(result, 'palette.mode', mode);
82
- set(result, 'mode', mode);
83
-
84
- return result;
85
- }, [mode, themeOptions]);
86
-
87
- const theme = useMemo(() => {
88
- return createTheme({ ..._themeOptions, disableBlockletTheme });
89
- }, [_themeOptions, disableBlockletTheme]);
90
-
91
- const toggleMode = useCallback(() => {
92
- const newMode = mode === 'light' ? 'dark' : 'light';
93
- setMode(newMode);
94
- localStorage.setItem(BLOCKLET_THEME_PREFER_KEY, newMode);
95
- }, [mode, setMode]);
96
-
97
- const config = useMemo(
98
- () => ({
99
- mode,
100
- themeOptions: _themeOptions,
101
- toggleMode,
102
- prefer,
103
- }),
104
- [mode, prefer, _themeOptions, toggleMode]
105
- );
106
-
107
- // change prefer manually
108
- useEffect(() => {
109
- if (prefer) {
110
- setMode(resolveMode(prefer));
111
- }
112
- }, [prefer, setMode]);
113
-
114
30
  return (
115
- <ConfigContext.Provider value={config}>
31
+ <ThemeProvider
32
+ theme={theme}
33
+ injectFirst={injectFirst}
34
+ darkSchemeClass={darkSchemeClass}
35
+ prefer={prefer}
36
+ disableBlockletTheme={disableBlockletTheme}
37
+ enableColorScheme={enableColorScheme}>
116
38
  <LocaleProvider
117
39
  locale={locale}
118
40
  fallbackLocale={fallbackLocale}
119
41
  translations={translations}
120
42
  onLoadingTranslation={onLoadingTranslation}
121
43
  languages={languages}>
122
- <ThemeProvider theme={theme} injectFirst={injectFirst}>
123
- <EmotionThemeProvider theme={theme}>{children}</EmotionThemeProvider>
124
- </ThemeProvider>
44
+ {children}
125
45
  </LocaleProvider>
126
- </ConfigContext.Provider>
46
+ </ThemeProvider>
127
47
  );
128
48
  }
129
49
 
130
50
  export function useConfig() {
131
51
  const theme = useTheme();
132
52
  const localeCtx = useLocaleContext();
133
- const configCtx = useContext(ConfigContext);
53
+ const colorSchemeCtx = useColorScheme();
134
54
 
135
55
  return {
136
56
  theme,
137
57
  ...localeCtx,
138
- ...configCtx,
58
+ ...colorSchemeCtx,
139
59
  };
140
60
  }
141
-
142
- ConfigProvider.useConfig = useConfig;
@@ -1,10 +1,10 @@
1
1
  import { IconButton } from '@mui/material';
2
2
  import LightModeOutlinedIcon from '@mui/icons-material/LightModeOutlined';
3
3
  import Brightness2OutlinedIcon from '@mui/icons-material/Brightness2Outlined';
4
- import { useConfig } from './config-provider';
4
+ import { useColorScheme } from '../Theme/theme-provider';
5
5
 
6
6
  export default function ThemeModeToggle() {
7
- const { mode, toggleMode, prefer } = useConfig();
7
+ const { mode, toggleMode, prefer } = useColorScheme();
8
8
 
9
9
  if (!toggleMode) {
10
10
  if (process.env.NODE_ENV !== 'production') {
@@ -1,7 +1,7 @@
1
1
  import { useRef } from 'react';
2
2
  import { Link } from 'react-router-dom';
3
3
  import { useCreation, useMemoizedFn } from 'ahooks';
4
- import { Box, BoxProps, Grid } from '@mui/material';
4
+ import { Box, BoxProps, Grid, useTheme } from '@mui/material';
5
5
  import SubItemGroup from './sub-item-group';
6
6
  import { Item } from './nav-menu';
7
7
  import { styled } from '../Theme';
@@ -26,6 +26,7 @@ import DidWalletSvg from './images/did-wallet.svg?react';
26
26
  import DidNameServiceSvg from './images/did-name-service.svg?react';
27
27
  import VCSvg from './images/vc.svg?react';
28
28
  import DidConnectSvg from './images/did-connect.svg?react';
29
+ import PaymentKitPng from './images/payment-kit.png';
29
30
 
30
31
  const translations = {
31
32
  en: {
@@ -54,6 +55,9 @@ const translations = {
54
55
  alKit: {
55
56
  description: 'Boost apps with AI',
56
57
  },
58
+ paymentKit: {
59
+ description: 'Effortless Crypto & Card Payments',
60
+ },
57
61
  blockletStore: {
58
62
  description: 'Discover & deploy apps',
59
63
  },
@@ -118,6 +122,9 @@ const translations = {
118
122
  alKit: {
119
123
  description: 'AI 赋能应用',
120
124
  },
125
+ paymentKit: {
126
+ description: '便捷的加密货币和银行卡支付',
127
+ },
121
128
  blockletStore: {
122
129
  description: '发现和部署应用程序',
123
130
  },
@@ -189,6 +196,7 @@ export interface ProductsProps extends BoxProps {
189
196
 
190
197
  export default function Products({ className, isOpen, ...rest }: ProductsProps) {
191
198
  const { mode } = useNavMenuContext();
199
+ const { palette } = useTheme();
192
200
  const wrapperRef = useRef<HTMLDivElement>(null);
193
201
  const { locale = 'en' } = useLocaleContext() || {};
194
202
  const t = useMemoizedFn((key, data = {}) => translate(translations, key, locale, 'en', data));
@@ -229,7 +237,7 @@ export default function Products({ className, isOpen, ...rest }: ProductsProps)
229
237
  </Link>
230
238
  ),
231
239
  description: t('products.aigne.description'),
232
- icon: <AigneSvg />,
240
+ icon: <AigneSvg style={{ filter: palette.mode === 'dark' ? 'invert(1)' : 'none' }} />,
233
241
  },
234
242
  {
235
243
  label: (
@@ -269,6 +277,15 @@ export default function Products({ className, isOpen, ...rest }: ProductsProps)
269
277
  description: t('products.alKit.description'),
270
278
  icon: <AIKitSvg />,
271
279
  },
280
+ {
281
+ label: (
282
+ <Link to={`https://www.blocklet.io/${locale}`} target="_blank" rel="noreferrer noopener">
283
+ Payment Kit
284
+ </Link>
285
+ ),
286
+ description: t('products.paymentKit.description'),
287
+ icon: <img src={PaymentKitPng} alt="Payment Kit" />,
288
+ },
272
289
  ],
273
290
  [
274
291
  {
@@ -332,7 +349,7 @@ export default function Products({ className, isOpen, ...rest }: ProductsProps)
332
349
  {
333
350
  label: (
334
351
  <Link
335
- to={`https://www.arcblock.io/content/collections/${locale}/blocklet-server`}
352
+ to={`https://www.blocklet.io/${locale}/blocklet-server`}
336
353
  target="_blank"
337
354
  rel="noreferrer noopener">
338
355
  Blocklet Server
@@ -418,7 +435,7 @@ export default function Products({ className, isOpen, ...rest }: ProductsProps)
418
435
  ],
419
436
  },
420
437
  ];
421
- }, [t, locale]);
438
+ }, [t, locale, palette]);
422
439
 
423
440
  return (
424
441
  <Wrapper ref={wrapperRef} className={`is-${mode} ${className}`} {...rest}>
@@ -128,7 +128,8 @@ export const NavMenuItem = styled('li', {
128
128
  width: '32px',
129
129
  height: '32px',
130
130
  marginRight: '16px',
131
- border: '1px solid #eff1f5',
131
+ border: '1px solid',
132
+ borderColor: theme.palette.grey[200],
132
133
  borderRadius: '4px',
133
134
  color: theme.palette.grey[500],
134
135
  },
@@ -200,7 +200,8 @@ export default function PhoneInput({
200
200
 
201
201
  // 预览模式
202
202
  if (preview) {
203
- const isValid = phone && validatePhoneNumber(phone, country);
203
+ const phoneWithCode = addCountryCodeToPhone(phone, getDialCodeByCountry(country));
204
+ const isValid = phone && validatePhoneNumber(phoneWithCode, country);
204
205
  const canDial = allowDial && isValid;
205
206
 
206
207
  return (
@@ -41,18 +41,18 @@ export default function QuickLoginItem({
41
41
  borderRadius: 1,
42
42
  p: 2,
43
43
  transition: 'background-color 0.5s',
44
+ bgcolor: 'background.paper',
44
45
  '&:hover, &:active': {
45
- backgroundColor: 'grey.100',
46
+ backgroundColor: 'action.hover',
46
47
  },
47
48
  display: 'flex',
48
49
  justifyContent: 'space-between',
49
50
  alignItems: 'center',
50
51
  cursor: 'pointer',
51
52
  '&:hover': {
52
- backgroundColor: 'grey.200',
53
+ backgroundColor: 'action.hover',
53
54
  },
54
55
  width: '100%',
55
- backgroundColor: 'white',
56
56
  }}
57
57
  onClick={onClick}>
58
58
  <Box
@@ -159,7 +159,7 @@ export default function UnLogin({ session, onLogin = noop, size = 24, dark = fal
159
159
  maxWidth: '90vw',
160
160
  borderColor: 'divider',
161
161
  border: '0 !important',
162
- boxShadow: `0px 8px 16px 0px ${palette.grey[200]}, 0px 0px 0px 1px ${palette.grey[200]}`,
162
+ boxShadow: 4,
163
163
  }}>
164
164
  <Box
165
165
  sx={{
@@ -167,7 +167,8 @@ export default function UnLogin({ session, onLogin = noop, size = 24, dark = fal
167
167
  alignItems: 'center',
168
168
  gap: 1,
169
169
  p: 2,
170
- borderBottom: '1px solid #eee',
170
+ borderBottom: '1px solid',
171
+ borderColor: 'divider',
171
172
  }}>
172
173
  {loginAppLogo && !currentState.loadAppLogoError ? (
173
174
  <img
@@ -213,8 +214,9 @@ export default function UnLogin({ session, onLogin = noop, size = 24, dark = fal
213
214
  overflow: 'hidden',
214
215
  position: 'relative',
215
216
  p: 0,
217
+ bgcolor: 'background.paper',
216
218
  '&:hover': {
217
- backgroundColor: `${palette.grey[200]} !important`,
219
+ bgcolor: `${palette.action.hover} !important`,
218
220
  },
219
221
  }}
220
222
  onClick={() => {
@@ -239,7 +241,6 @@ export default function UnLogin({ session, onLogin = noop, size = 24, dark = fal
239
241
  sx={{
240
242
  mx: 2,
241
243
  my: '0px !important',
242
- borderColor: '#e4e4e7',
243
244
  }}
244
245
  />
245
246
  ) : null}
@@ -1,4 +1,4 @@
1
- import { alpha, Box, Chip, Typography, useTheme } from '@mui/material';
1
+ import { alpha, Box, Chip, Typography } from '@mui/material';
2
2
  import { Icon } from '@iconify/react';
3
3
  import SwapHorizRoundedIcon from '@iconify-icons/material-symbols/swap-horiz-rounded';
4
4
  import ArrowRightAltRoundedIcon from '@iconify-icons/material-symbols/arrow-right-alt-rounded';
@@ -2,6 +2,7 @@ import type { CreateMUIStyled, Theme } from '@mui/material';
2
2
  import { styled as muiStyled, useTheme } from '@mui/material';
3
3
 
4
4
  export * from './theme';
5
+ export * from './theme-provider';
5
6
  export { default as ThemeProvider } from './theme-provider';
6
7
  export { useTheme };
7
8
 
@@ -1,12 +1,42 @@
1
+ import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
1
2
  import PropTypes from 'prop-types';
2
- import { GlobalStyles } from '@mui/material';
3
+ import { GlobalStyles, PaletteMode } from '@mui/material';
3
4
  import { ThemeProvider as MuiThemeProvider, Theme, useTheme } from '@mui/material/styles';
4
5
  import StyledEngineProvider from '@mui/material/StyledEngineProvider';
5
6
  import CssBaseline from '@mui/material/CssBaseline';
6
- import { createTheme } from './theme';
7
+ import set from 'lodash/set';
8
+ import { BLOCKLET_THEME_PREFER_KEY } from '@blocklet/theme';
9
+
10
+ import { createTheme, getDefaultThemePrefer, isTheme, lazyThemeConfig, type UserThemeOptions } from './theme';
7
11
 
8
12
  const defaultTheme = createTheme();
9
13
 
14
+ /** 颜色模式上下文类型 */
15
+ export interface ColorSchemeContextType {
16
+ mode: PaletteMode;
17
+ toggleMode: () => void;
18
+ prefer?: Prefer;
19
+ }
20
+
21
+ export const ColorSchemeContext = createContext<ColorSchemeContextType>({} as ColorSchemeContextType);
22
+ export function useColorScheme() {
23
+ return useContext(ColorSchemeContext);
24
+ }
25
+
26
+ /** 根据偏好获取颜色模式 */
27
+ const resolveMode = (prefer?: Prefer): PaletteMode => {
28
+ if (prefer) {
29
+ if (prefer === 'system') {
30
+ // 取系统默认
31
+ return getDefaultThemePrefer({ theme: { prefer: 'system' } });
32
+ }
33
+ return prefer;
34
+ }
35
+
36
+ return getDefaultThemePrefer();
37
+ };
38
+
39
+ /** 深色模式全局样式 */
10
40
  function DarkSchemeStyles({ className }: { className?: string }) {
11
41
  const theme = useTheme();
12
42
 
@@ -72,22 +102,35 @@ function DarkSchemeStyles({ className }: { className?: string }) {
72
102
  return null;
73
103
  }
74
104
 
75
- export interface ThemeProviderProps {
105
+ export type UxTheme = Partial<Theme> | ((outerTheme: Partial<Theme>) => Theme);
106
+ export type Prefer = 'light' | 'dark' | 'system';
107
+
108
+ interface BaseThemeProviderProps {
76
109
  children?: React.ReactNode;
77
- theme: Theme;
110
+ theme?: UxTheme;
78
111
  injectFirst?: boolean;
79
112
  /** 指定一个类名,DarkSchemeStyles 只会作用于带有该类的元素及其后代 */
80
113
  darkSchemeClass?: string;
81
114
  }
82
115
 
83
- /**
84
- * 默认的 theme provider, 可以为 webapp/blocklet 快捷的配置好 mui theme provider
85
- */
86
- export default function ThemeProvider({ children, theme, injectFirst, darkSchemeClass }: ThemeProviderProps) {
116
+ /** 基础的 theme provider, 可以为 webapp/blocklet 快捷的配置好 mui theme provider */
117
+ function BaseThemeProvider({
118
+ children,
119
+ theme = defaultTheme,
120
+ injectFirst = true,
121
+ darkSchemeClass = '',
122
+ }: BaseThemeProviderProps) {
123
+ const _theme = useMemo(() => {
124
+ if (isTheme(theme)) return theme;
125
+
126
+ // 是 ThemeOptions 则创建一个 theme
127
+ return createTheme(theme);
128
+ }, [theme]);
129
+
87
130
  return (
88
131
  // injectFirst 会影响 makeStyles 自定义样式和 mui styles 覆盖问题
89
132
  <StyledEngineProvider injectFirst={injectFirst}>
90
- <MuiThemeProvider theme={theme}>
133
+ <MuiThemeProvider theme={_theme}>
91
134
  <CssBaseline />
92
135
  <DarkSchemeStyles className={darkSchemeClass} />
93
136
  {children}
@@ -96,16 +139,101 @@ export default function ThemeProvider({ children, theme, injectFirst, darkScheme
96
139
  );
97
140
  }
98
141
 
142
+ interface ColorSchemeProviderProps extends BaseThemeProviderProps {
143
+ prefer?: Prefer;
144
+ disableBlockletTheme?: boolean;
145
+ }
146
+
147
+ /** 带颜色模式切换功能的 theme provider */
148
+ function ColorSchemeProvider({
149
+ children,
150
+ theme: themeInput,
151
+ prefer,
152
+ disableBlockletTheme = false,
153
+ ...rest
154
+ }: ThemeProviderProps) {
155
+ const [mode, setMode] = useState<PaletteMode>(() => resolveMode(prefer));
156
+
157
+ const _themeInput = useMemo(() => {
158
+ let result: UserThemeOptions = {};
159
+ const getThemeConfig = lazyThemeConfig(mode);
160
+
161
+ if (themeInput) {
162
+ if (typeof themeInput === 'function') {
163
+ result = { ...themeInput(getThemeConfig()) };
164
+ } else {
165
+ result = { ...themeInput };
166
+ }
167
+ }
168
+
169
+ set(result, 'palette.mode', mode);
170
+ set(result, 'mode', mode);
171
+
172
+ return result;
173
+ }, [mode, themeInput]);
174
+
175
+ const theme = useMemo(() => {
176
+ return createTheme({ ..._themeInput, disableBlockletTheme });
177
+ }, [_themeInput, disableBlockletTheme]);
178
+
179
+ // 切换明/暗模式
180
+ const toggleMode = useCallback(() => {
181
+ const newMode = mode === 'light' ? 'dark' : 'light';
182
+ setMode(newMode);
183
+ localStorage.setItem(BLOCKLET_THEME_PREFER_KEY, newMode);
184
+ }, [mode, setMode]);
185
+
186
+ const colorSchemeValue = useMemo(
187
+ () => ({
188
+ mode,
189
+ toggleMode,
190
+ prefer,
191
+ }),
192
+ [mode, prefer, toggleMode]
193
+ );
194
+
195
+ useEffect(() => {
196
+ if (prefer) {
197
+ setMode(resolveMode(prefer));
198
+ }
199
+ }, [prefer, setMode]);
200
+
201
+ return (
202
+ <ColorSchemeContext.Provider value={colorSchemeValue}>
203
+ <BaseThemeProvider theme={theme} {...rest}>
204
+ {children}
205
+ </BaseThemeProvider>
206
+ </ColorSchemeContext.Provider>
207
+ );
208
+ }
209
+
210
+ export interface ThemeProviderProps extends ColorSchemeProviderProps {
211
+ /** 下列情况会启用 ColorScheme 功能(让 theme 支持明暗模式切换)
212
+ * 1. 显示打开 enableColorScheme
213
+ * 2. 显示设置 prefer
214
+ * 3. 顶层 ThemeProvider
215
+ */
216
+ enableColorScheme?: boolean;
217
+ }
218
+
219
+ export default function ThemeProvider({ children, prefer, enableColorScheme = false, ...props }: ThemeProviderProps) {
220
+ const { toggleMode } = useColorScheme();
221
+
222
+ if (enableColorScheme || prefer || !toggleMode) {
223
+ return (
224
+ <ColorSchemeProvider prefer={prefer} {...props}>
225
+ {children}
226
+ </ColorSchemeProvider>
227
+ );
228
+ }
229
+
230
+ return <BaseThemeProvider {...props}>{children}</BaseThemeProvider>;
231
+ }
232
+
99
233
  ThemeProvider.propTypes = {
100
234
  children: PropTypes.any,
101
235
  theme: PropTypes.any,
102
236
  injectFirst: PropTypes.bool,
103
237
  darkSchemeClass: PropTypes.string,
104
- };
105
-
106
- ThemeProvider.defaultProps = {
107
- children: null,
108
- theme: defaultTheme,
109
- injectFirst: true,
110
- darkSchemeClass: '',
238
+ enableColorScheme: PropTypes.bool,
111
239
  };
@@ -3,7 +3,6 @@
3
3
  import type { PaletteMode, Theme } from '@mui/material';
4
4
  import { createTheme as _createTheme, responsiveFontSizes, type ThemeOptions } from '@mui/material/styles';
5
5
  import { deepmerge } from '@mui/utils';
6
- import pick from 'lodash/pick';
7
6
  import webfontloader from 'webfontloader';
8
7
  import { BLOCKLET_THEME_LIGHT, BLOCKLET_THEME_DARK, DEFAULT_FONTS, BLOCKLET_THEME_PREFER_KEY } from '@blocklet/theme';
9
8
  import { cleanedObj, deepmergeAll } from '../Util';
@@ -16,6 +15,11 @@ import '@fontsource/roboto/latin-ext-400.css';
16
15
  import '@fontsource/roboto/latin-ext-500.css';
17
16
  import '@fontsource/roboto/latin-ext-700.css';
18
17
 
18
+ /** 是否是 MUI Theme 对象 */
19
+ export function isTheme(obj: any): obj is Theme {
20
+ return obj && typeof obj === 'object' && obj.palette && typeof obj.palette.getContrastText === 'function';
21
+ }
22
+
19
23
  // 收集字体配置
20
24
  export function collectFontFamilies(obj?: { fontFamily?: string }, fontSet: Set<string> = new Set()): Set<string> {
21
25
  if (!obj || typeof obj !== 'object') return fontSet;
@@ -108,18 +112,13 @@ export interface UserThemeOptions extends ThemeOptions {
108
112
  }
109
113
 
110
114
  // 用于获取 Blocklet Theme 配置,便于用户创建自定义主题
111
- export type ThemeConfig = Pick<Theme, 'palette'>;
112
115
  export function lazyThemeConfig(mode: PaletteMode) {
113
- const fields = ['palette'];
114
- let config: ThemeConfig | null = null;
116
+ let config: Partial<Theme> | null = null;
115
117
 
116
118
  return () => {
117
119
  if (config) return config;
118
120
 
119
- config = deepmerge(
120
- pick(createDefaultThemeOptions(mode), fields),
121
- pick(window.blocklet?.theme?.[mode] ?? {}, fields)
122
- ) as ThemeConfig;
121
+ config = deepmerge(createDefaultThemeOptions(mode), window.blocklet?.theme?.[mode] ?? {}) as Partial<Theme>;
123
122
 
124
123
  return config;
125
124
  };
@@ -167,7 +166,7 @@ const defaultUserThemeOptions: UserThemeOptions = {
167
166
  };
168
167
 
169
168
  // https://material-ui.com/customization/default-theme/
170
- export const create = (...args: Array<UserThemeOptions | ((config: ThemeConfig) => UserThemeOptions)>) => {
169
+ export const create = (...args: Array<UserThemeOptions | ((config: Partial<Theme>) => UserThemeOptions)>) => {
171
170
  const defaultPrefer = getDefaultThemePrefer();
172
171
  const getThemeConfig = lazyThemeConfig(defaultPrefer);
173
172
  const userThemeOptions = args.reduce<UserThemeOptions>(