@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.
- package/lib/Config/config-provider.d.ts +6 -23
- package/lib/Config/config-provider.js +19 -80
- package/lib/Config/theme-mode-toggle.js +2 -2
- package/lib/NavMenu/images/payment-kit.png +0 -0
- package/lib/NavMenu/products.js +30 -4
- package/lib/NavMenu/style.js +2 -1
- package/lib/PhoneInput/index.js +2 -1
- package/lib/SessionUser/components/quick-login-item.js +4 -4
- package/lib/SessionUser/components/un-login.js +6 -5
- package/lib/Theme/index.d.ts +1 -0
- package/lib/Theme/index.js +1 -0
- package/lib/Theme/theme-provider.d.ts +27 -12
- package/lib/Theme/theme-provider.js +123 -16
- package/lib/Theme/theme.d.ts +5 -4
- package/lib/Theme/theme.js +6 -5
- package/package.json +6 -6
- package/src/Config/config-provider.tsx +21 -103
- package/src/Config/theme-mode-toggle.tsx +2 -2
- package/src/NavMenu/images/payment-kit.png +0 -0
- package/src/NavMenu/products.tsx +21 -4
- package/src/NavMenu/style.ts +2 -1
- package/src/PhoneInput/index.tsx +2 -1
- package/src/SessionUser/components/quick-login-item.tsx +3 -3
- package/src/SessionUser/components/un-login.tsx +5 -4
- package/src/SessionUser/components/user-info.tsx +1 -1
- package/src/Theme/index.ts +1 -0
- package/src/Theme/theme-provider.tsx +144 -16
- package/src/Theme/theme.ts +8 -9
package/lib/Theme/theme.js
CHANGED
@@ -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(
|
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.
|
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": "
|
74
|
+
"gitHead": "57fa9683f81c72474528412d1a2205dab186f4f9",
|
75
75
|
"dependencies": {
|
76
76
|
"@arcblock/did-motif": "^1.1.13",
|
77
|
-
"@arcblock/icons": "^2.13.
|
78
|
-
"@arcblock/nft-display": "^2.13.
|
79
|
-
"@arcblock/react-hooks": "^2.13.
|
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.
|
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 {
|
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
|
-
|
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
|
16
|
+
// theme provider
|
17
|
+
theme,
|
18
|
+
injectFirst,
|
19
|
+
darkSchemeClass,
|
56
20
|
prefer,
|
57
|
-
theme: themeOptions,
|
58
21
|
disableBlockletTheme = false,
|
59
|
-
|
60
|
-
// locale
|
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
|
-
<
|
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
|
-
|
123
|
-
<EmotionThemeProvider theme={theme}>{children}</EmotionThemeProvider>
|
124
|
-
</ThemeProvider>
|
44
|
+
{children}
|
125
45
|
</LocaleProvider>
|
126
|
-
</
|
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
|
53
|
+
const colorSchemeCtx = useColorScheme();
|
134
54
|
|
135
55
|
return {
|
136
56
|
theme,
|
137
57
|
...localeCtx,
|
138
|
-
...
|
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 {
|
4
|
+
import { useColorScheme } from '../Theme/theme-provider';
|
5
5
|
|
6
6
|
export default function ThemeModeToggle() {
|
7
|
-
const { mode, toggleMode, prefer } =
|
7
|
+
const { mode, toggleMode, prefer } = useColorScheme();
|
8
8
|
|
9
9
|
if (!toggleMode) {
|
10
10
|
if (process.env.NODE_ENV !== 'production') {
|
Binary file
|
package/src/NavMenu/products.tsx
CHANGED
@@ -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.
|
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}>
|
package/src/NavMenu/style.ts
CHANGED
@@ -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
|
131
|
+
border: '1px solid',
|
132
|
+
borderColor: theme.palette.grey[200],
|
132
133
|
borderRadius: '4px',
|
133
134
|
color: theme.palette.grey[500],
|
134
135
|
},
|
package/src/PhoneInput/index.tsx
CHANGED
@@ -200,7 +200,8 @@ export default function PhoneInput({
|
|
200
200
|
|
201
201
|
// 预览模式
|
202
202
|
if (preview) {
|
203
|
-
const
|
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: '
|
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: '
|
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:
|
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
|
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
|
-
|
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
|
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';
|
package/src/Theme/index.ts
CHANGED
@@ -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
|
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
|
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
|
110
|
+
theme?: UxTheme;
|
78
111
|
injectFirst?: boolean;
|
79
112
|
/** 指定一个类名,DarkSchemeStyles 只会作用于带有该类的元素及其后代 */
|
80
113
|
darkSchemeClass?: string;
|
81
114
|
}
|
82
115
|
|
83
|
-
/**
|
84
|
-
|
85
|
-
|
86
|
-
|
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={
|
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
|
};
|
package/src/Theme/theme.ts
CHANGED
@@ -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
|
-
|
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:
|
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>(
|