@arcblock/ux 2.12.54 → 2.12.56

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.
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { createContext, useContext, useMemo, useState, useCallback } from 'react';
2
+ import { createContext, useContext, useMemo, useState, useCallback, useEffect } from 'react';
3
3
  import useMediaQuery from '@mui/material/useMediaQuery';
4
4
  import set from 'lodash/set';
5
5
  import { LocaleProvider, useLocaleContext } from '../Locale/context';
@@ -28,11 +28,14 @@ export function ConfigProvider({
28
28
  }) {
29
29
  const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)');
30
30
  const [mode, setMode] = useState(() => {
31
- const preferMode = localStorage.getItem(preferThemeModeKey);
32
- if (preferMode && (preferMode === 'light' || preferMode === 'dark')) {
33
- return preferMode;
31
+ if (prefer) {
32
+ return prefer;
34
33
  }
35
- return prefer || (prefersDarkMode ? 'dark' : 'light');
34
+ const localPrefer = localStorage.getItem(preferThemeModeKey);
35
+ if (localPrefer && (localPrefer === 'light' || localPrefer === 'dark')) {
36
+ return localPrefer;
37
+ }
38
+ return prefersDarkMode ? 'dark' : 'light';
36
39
  });
37
40
  const _themeOptions = useMemo(() => {
38
41
  let result = {};
@@ -64,6 +67,13 @@ export function ConfigProvider({
64
67
  themeOptions: _themeOptions,
65
68
  toggleMode
66
69
  }), [mode, _themeOptions, toggleMode]);
70
+
71
+ // change prefer manually
72
+ useEffect(() => {
73
+ if (prefer) {
74
+ setMode(prefer);
75
+ }
76
+ }, [prefer, setMode]);
67
77
  return /*#__PURE__*/_jsx(ConfigContext.Provider, {
68
78
  value: config,
69
79
  children: /*#__PURE__*/_jsx(LocaleProvider, {
@@ -32,6 +32,7 @@ export default function CustomToolbar(props) {
32
32
  const toolbarId = useRef(Math.random().toString(32).slice(2));
33
33
  const [searchOpened, setSearchOpened] = useState(false);
34
34
  const {
35
+ customPreButtons,
35
36
  customButtons,
36
37
  loading,
37
38
  disabled
@@ -129,12 +130,12 @@ export default function CustomToolbar(props) {
129
130
  }
130
131
  });
131
132
  }
132
- const showMore = [!hidePrint, ...defaultButtons, ...customButtons].filter(e => !!e).length > 1 && isMobile;
133
+ const showMore = [...customPreButtons, !hidePrint, ...defaultButtons, ...customButtons].filter(e => !!e).length > 1 && isMobile;
133
134
  const allPops = [];
134
135
 
135
136
  // Large screens show the toolbar buttons directly, small screens show the drop-down menu style buttons
136
137
  // The right-hand button of the form toolbar in desktop mode
137
- const toolbarButtons = [...defaultButtons, ...customButtons].map((e, index) => {
138
+ const toolbarButtons = [...customPreButtons, ...defaultButtons, ...customButtons].map((e, index) => {
138
139
  if (/*#__PURE__*/isValidElement(e)) {
139
140
  return e;
140
141
  }
@@ -189,7 +190,7 @@ export default function CustomToolbar(props) {
189
190
  });
190
191
 
191
192
  // The toolbar menu in the mobile to replace toolbarButtons
192
- const menuItems = [...defaultButtons, ...customButtons].map((e, index) => {
193
+ const menuItems = [...customPreButtons, ...defaultButtons, ...customButtons].map((e, index) => {
193
194
  const popId = getPopId(index);
194
195
  let content;
195
196
  if (/*#__PURE__*/isValidElement(e)) {
@@ -10,12 +10,15 @@ function DatatableProvider({
10
10
  children
11
11
  }) {
12
12
  const [customButtons, setCustomButtons] = useState([]);
13
+ const [customPreButtons, setCustomPreButtons] = useState([]);
13
14
  const [loading, setLoading] = useState(false);
14
15
  const [disabled, setDisabled] = useState(false);
15
16
  const [filterLabel, setFilterLabel] = useState('Filter');
16
17
  const value = {
17
18
  customButtons,
18
19
  setCustomButtons,
20
+ customPreButtons,
21
+ setCustomPreButtons,
19
22
  filterLabel,
20
23
  setFilterLabel,
21
24
  loading,
@@ -29,6 +29,7 @@ export type DataTableProps = {
29
29
  } & import("mui-datatables").MUIDataTableOptions;
30
30
  style?: import("react").CSSProperties;
31
31
  customButtons?: Array<DataTableCustomButton>;
32
+ customPreButtons?: Array<DataTableCustomButton>;
32
33
  onChange?: (state: DataTableState, action: string) => void | Promise<void>;
33
34
  loading?: false | true;
34
35
  disabled?: false | true;
@@ -48,6 +48,7 @@ import { styled } from '../Theme';
48
48
  * options?: { searchDebounceTime?: number } & import('mui-datatables').MUIDataTableOptions,
49
49
  * style?: import('react').CSSProperties,
50
50
  * customButtons?: Array<DataTableCustomButton>,
51
+ * customPreButtons?: Array<DataTableCustomButton>,
51
52
  * onChange?: (state: DataTableState, action: string) => void | Promise<void>,
52
53
  * loading?: false | true,
53
54
  * disabled?: false | true,
@@ -166,6 +167,7 @@ function ReDataTable({
166
167
  options,
167
168
  // The options object is usually a property supported by mui-datatable
168
169
  style,
170
+ customPreButtons,
169
171
  customButtons,
170
172
  onChange,
171
173
  loading,
@@ -181,6 +183,7 @@ function ReDataTable({
181
183
  }) {
182
184
  const oldState = useRef(null);
183
185
  const {
186
+ setCustomPreButtons,
184
187
  setCustomButtons,
185
188
  setFilterLabel,
186
189
  setLoading,
@@ -271,6 +274,7 @@ function ReDataTable({
271
274
  return e;
272
275
  });
273
276
  useEffect(() => setCustomButtons(customButtons || []), [customButtons]);
277
+ useEffect(() => setCustomPreButtons(customPreButtons || []), [customPreButtons]);
274
278
  useEffect(() => setLoading(loading), [loading]);
275
279
  useEffect(() => setDisabled(disabled), [disabled]);
276
280
  let emptyEl;
@@ -435,6 +439,7 @@ ReDataTable.propTypes = {
435
439
  locale: PropTypes.string,
436
440
  loading: PropTypes.bool,
437
441
  disabled: PropTypes.bool,
442
+ customPreButtons: PropTypes.array,
438
443
  customButtons: PropTypes.array,
439
444
  onChange: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
440
445
  stripped: PropTypes.bool,
@@ -451,6 +456,7 @@ ReDataTable.defaultProps = {
451
456
  locale: 'en',
452
457
  loading: false,
453
458
  disabled: false,
459
+ customPreButtons: [],
454
460
  customButtons: [],
455
461
  onChange: '',
456
462
  stripped: false,
@@ -1,13 +1,8 @@
1
1
  import { Components, type ThemeOptions } from '@mui/material/styles';
2
2
  import type { Typography } from '@mui/material/styles/createTypography';
3
- import '@fontsource/inter/latin-300.css';
4
- import '@fontsource/inter/latin-400.css';
5
- import '@fontsource/inter/latin-500.css';
6
- import '@fontsource/inter/latin-700.css';
7
- import '@fontsource/inter/latin-ext-300.css';
8
- import '@fontsource/inter/latin-ext-400.css';
9
- import '@fontsource/inter/latin-ext-500.css';
10
- import '@fontsource/inter/latin-ext-700.css';
3
+ import '@fontsource/roboto/400';
4
+ import '@fontsource/roboto/500';
5
+ import '@fontsource/roboto/700';
11
6
  import { ThemeMode } from '../type';
12
7
  declare module '@mui/material/styles' {
13
8
  interface Theme {
@@ -39,11 +34,12 @@ declare module '@mui/material/styles/createTypography' {
39
34
  interface TypographyOptions {
40
35
  useNextVariants?: boolean;
41
36
  color?: Record<string, string>;
42
- button?: {
43
- fontWeight?: number;
44
- };
45
37
  }
46
38
  }
39
+ export declare function collectFontFamilies(obj?: {
40
+ fontFamily?: string;
41
+ }, fontSet?: Set<string>): Set<string>;
42
+ export declare function loadFonts(fonts: string[]): Promise<boolean>;
47
43
  export declare function createDefaultThemeOptions(mode?: ThemeMode): ThemeOptions;
48
44
  export declare const create: ({ mode, pageWidth, overrides, palette, components, ...rest }?: ThemeOptions) => import("@mui/material/styles").Theme;
49
45
  export declare const createTheme: ({ mode, pageWidth, overrides, palette, components, ...rest }?: ThemeOptions) => import("@mui/material/styles").Theme;
@@ -2,15 +2,14 @@
2
2
  // https://app.zeplin.io/styleguide/5d1436f1e97c2156f49c0725/colors
3
3
  import { createTheme as _createTheme, responsiveFontSizes } from '@mui/material/styles';
4
4
  import { deepmerge } from '@mui/utils';
5
- // 为了避免加载全量的字体导致打包后体积太大,目前只选择了 latin 语系的字体
6
- import '@fontsource/inter/latin-300.css';
7
- import '@fontsource/inter/latin-400.css';
8
- import '@fontsource/inter/latin-500.css';
9
- import '@fontsource/inter/latin-700.css';
10
- import '@fontsource/inter/latin-ext-300.css';
11
- import '@fontsource/inter/latin-ext-400.css';
12
- import '@fontsource/inter/latin-ext-500.css';
13
- import '@fontsource/inter/latin-ext-700.css';
5
+ import webfontloader from 'webfontloader';
6
+ // 为了避免加载全量的字体导致打包后体积太大,目前只选择了 MUI 默认的 Roboto 字体
7
+ // eslint-disable-next-line import/no-unresolved
8
+ import '@fontsource/roboto/400';
9
+ // eslint-disable-next-line import/no-unresolved
10
+ import '@fontsource/roboto/500';
11
+ // eslint-disable-next-line import/no-unresolved
12
+ import '@fontsource/roboto/700';
14
13
  import colors from '../Colors';
15
14
  import { cleanedObj } from '../Util';
16
15
 
@@ -18,12 +17,56 @@ import { cleanedObj } from '../Util';
18
17
 
19
18
  // 扩展 TypographyOptions
20
19
 
21
- const muiDarkTheme = _createTheme({
20
+ // 默认深色主题
21
+ const defaultDarkTheme = _createTheme({
22
22
  palette: {
23
23
  mode: 'dark'
24
24
  }
25
25
  });
26
- const DEFAULT_FONT_FAMILY = ['Inter', 'Avenir', '-apple-system', 'BlinkMacSystemFont', '"Segoe UI"', 'Roboto', '"Helvetica Neue"', 'Arial', 'sans-serif', '"Apple Color Emoji"', '"Segoe UI Emoji"', '"Segoe UI Symbol"'].join(',');
26
+
27
+ // 收集字体配置
28
+ export function collectFontFamilies(obj, fontSet = new Set()) {
29
+ if (!obj || typeof obj !== 'object') return fontSet;
30
+ if (typeof obj.fontFamily === 'string') {
31
+ obj.fontFamily.replace(/"/g, '').split(',').map(font => font.trim()).filter(Boolean).forEach(font => fontSet.add(font));
32
+ }
33
+ Object.values(obj).forEach(value => {
34
+ if (typeof value === 'object') {
35
+ collectFontFamilies(value, fontSet);
36
+ }
37
+ });
38
+ return fontSet;
39
+ }
40
+
41
+ // 动态加载字体
42
+ const prevFonts = new Set(['Roboto', 'inherit']);
43
+ export function loadFonts(fonts) {
44
+ // 过滤出未加载的字体
45
+ const unloadedFonts = fonts.filter(font => !prevFonts.has(font));
46
+
47
+ // 如果所有字体都已加载,直接返回
48
+ if (unloadedFonts.length === 0) {
49
+ return Promise.resolve(true);
50
+ }
51
+
52
+ // record
53
+ unloadedFonts.forEach(font => prevFonts.add(font));
54
+ return new Promise(resolve => {
55
+ webfontloader.load({
56
+ google: {
57
+ families: unloadedFonts
58
+ },
59
+ active: () => resolve(true),
60
+ inactive: () => resolve(true),
61
+ fontinactive: (familyName, fvd) => {
62
+ prevFonts.delete(familyName);
63
+ console.warn(`font ${familyName} ${fvd} download failed`);
64
+ }
65
+ });
66
+ });
67
+ }
68
+
69
+ // 创建默认主题配置
27
70
  export function createDefaultThemeOptions(mode = 'light') {
28
71
  const result = {
29
72
  palette: {
@@ -44,7 +87,6 @@ export function createDefaultThemeOptions(mode = 'light') {
44
87
  main: mode === 'light' ? '#222222' : colors.common.white,
45
88
  gray: mode === 'light' ? colors.grey[500] : colors.grey[300]
46
89
  },
47
- fontFamily: DEFAULT_FONT_FAMILY,
48
90
  // button 默认使用粗体
49
91
  button: {
50
92
  fontWeight: 700
@@ -95,10 +137,11 @@ export function createDefaultThemeOptions(mode = 'light') {
95
137
  }
96
138
  }
97
139
  };
140
+
98
141
  // 深色主题
99
142
  if (mode === 'dark') {
100
143
  result.palette = {
101
- ...muiDarkTheme.palette,
144
+ ...defaultDarkTheme.palette,
102
145
  background: {
103
146
  paper: colors.grey[900],
104
147
  default: colors.grey[900]
@@ -163,6 +206,10 @@ export const create = ({
163
206
  const mergedThemeOptions = deepmerge(deepmerge(defaultThemeOptions, cleanedObj(blockletThemeOptions)), cleanedObj(userThemeOptions));
164
207
  const theme = _createTheme(mergedThemeOptions);
165
208
 
209
+ // 异步加载字体
210
+ const fonts = collectFontFamilies(theme.typography);
211
+ loadFonts(Array.from(fonts));
212
+
166
213
  /**
167
214
  * 响应式字体,配置后,theme.typography 会变为下面的结构
168
215
  * {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcblock/ux",
3
- "version": "2.12.54",
3
+ "version": "2.12.56",
4
4
  "description": "Common used react components for arcblock products",
5
5
  "keywords": [
6
6
  "react",
@@ -47,6 +47,7 @@
47
47
  "@types/pako": "^2.0.3",
48
48
  "@types/react": "^18.3.4",
49
49
  "@types/react-helmet": "^6.1.11",
50
+ "@types/webfontloader": "^1.6.38",
50
51
  "@typescript-eslint/eslint-plugin": "^8.7.0",
51
52
  "@typescript-eslint/parser": "^8.7.0",
52
53
  "babel-jest": "29",
@@ -69,14 +70,14 @@
69
70
  "react": ">=18.2.0",
70
71
  "react-router-dom": ">=6.22.3"
71
72
  },
72
- "gitHead": "fa8ee46123aa7dc5b6a79a9c75d08cb43857ad2c",
73
+ "gitHead": "dad60e296d45cc578ede5c25e2c051da0d5e3c5a",
73
74
  "dependencies": {
74
75
  "@arcblock/did-motif": "^1.1.13",
75
- "@arcblock/icons": "^2.12.54",
76
- "@arcblock/nft-display": "^2.12.54",
77
- "@arcblock/react-hooks": "^2.12.54",
76
+ "@arcblock/icons": "^2.12.56",
77
+ "@arcblock/nft-display": "^2.12.56",
78
+ "@arcblock/react-hooks": "^2.12.56",
78
79
  "@babel/plugin-syntax-dynamic-import": "^7.8.3",
79
- "@fontsource/inter": "^5.0.16",
80
+ "@fontsource/roboto": "^5.2.5",
80
81
  "@fontsource/ubuntu-mono": "^5.0.18",
81
82
  "@iconify-icons/logos": "^1.2.36",
82
83
  "@iconify-icons/material-symbols": "^1.2.58",
@@ -123,6 +124,7 @@
123
124
  "topojson-client": "^3.1.0",
124
125
  "type-fest": "^4.28.0",
125
126
  "validator": "^13.9.0",
126
- "versor": "^0.0.4"
127
+ "versor": "^0.0.4",
128
+ "webfontloader": "^1.6.28"
127
129
  }
128
130
  }
@@ -1,4 +1,4 @@
1
- import { createContext, useContext, ReactNode, useMemo, useState, useCallback } from 'react';
1
+ import { createContext, useContext, ReactNode, useMemo, useState, useCallback, useEffect } from 'react';
2
2
  import type { ThemeOptions } from '@mui/material/styles';
3
3
  import useMediaQuery from '@mui/material/useMediaQuery';
4
4
  import set from 'lodash/set';
@@ -49,11 +49,16 @@ export function ConfigProvider({
49
49
  }: ConfigProviderProps) {
50
50
  const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)');
51
51
  const [mode, setMode] = useState<ThemeMode>(() => {
52
- const preferMode = localStorage.getItem(preferThemeModeKey) as ThemeMode;
53
- if (preferMode && (preferMode === 'light' || preferMode === 'dark')) {
54
- return preferMode;
52
+ if (prefer) {
53
+ return prefer;
55
54
  }
56
- return prefer || (prefersDarkMode ? 'dark' : 'light');
55
+
56
+ const localPrefer = localStorage.getItem(preferThemeModeKey) as ThemeMode;
57
+ if (localPrefer && (localPrefer === 'light' || localPrefer === 'dark')) {
58
+ return localPrefer;
59
+ }
60
+
61
+ return prefersDarkMode ? 'dark' : 'light';
57
62
  });
58
63
 
59
64
  const _themeOptions = useMemo(() => {
@@ -92,6 +97,13 @@ export function ConfigProvider({
92
97
  [mode, _themeOptions, toggleMode]
93
98
  );
94
99
 
100
+ // change prefer manually
101
+ useEffect(() => {
102
+ if (prefer) {
103
+ setMode(prefer);
104
+ }
105
+ }, [prefer, setMode]);
106
+
95
107
  return (
96
108
  <ConfigContext.Provider value={config}>
97
109
  <LocaleProvider
@@ -32,7 +32,7 @@ export default function CustomToolbar(props) {
32
32
  const isMobile = useMobile();
33
33
  const toolbarId = useRef(Math.random().toString(32).slice(2));
34
34
  const [searchOpened, setSearchOpened] = useState(false);
35
- const { customButtons, loading, disabled } = useDatatableContext();
35
+ const { customPreButtons, customButtons, loading, disabled } = useDatatableContext();
36
36
 
37
37
  const {
38
38
  data,
@@ -136,13 +136,14 @@ export default function CustomToolbar(props) {
136
136
  });
137
137
  }
138
138
 
139
- const showMore = [!hidePrint, ...defaultButtons, ...customButtons].filter((e) => !!e).length > 1 && isMobile;
139
+ const showMore =
140
+ [...customPreButtons, !hidePrint, ...defaultButtons, ...customButtons].filter((e) => !!e).length > 1 && isMobile;
140
141
 
141
142
  const allPops = [];
142
143
 
143
144
  // Large screens show the toolbar buttons directly, small screens show the drop-down menu style buttons
144
145
  // The right-hand button of the form toolbar in desktop mode
145
- const toolbarButtons = [...defaultButtons, ...customButtons].map((e, index) => {
146
+ const toolbarButtons = [...customPreButtons, ...defaultButtons, ...customButtons].map((e, index) => {
146
147
  if (isValidElement(e)) {
147
148
  return e;
148
149
  }
@@ -198,7 +199,7 @@ export default function CustomToolbar(props) {
198
199
  });
199
200
 
200
201
  // The toolbar menu in the mobile to replace toolbarButtons
201
- const menuItems = [...defaultButtons, ...customButtons].map((e, index) => {
202
+ const menuItems = [...customPreButtons, ...defaultButtons, ...customButtons].map((e, index) => {
202
203
  const popId = getPopId(index);
203
204
 
204
205
  let content;
@@ -7,6 +7,7 @@ const { Provider } = DatatableContext;
7
7
  // eslint-disable-next-line react/prop-types
8
8
  function DatatableProvider({ children }) {
9
9
  const [customButtons, setCustomButtons] = useState([]);
10
+ const [customPreButtons, setCustomPreButtons] = useState([]);
10
11
  const [loading, setLoading] = useState(false);
11
12
  const [disabled, setDisabled] = useState(false);
12
13
  const [filterLabel, setFilterLabel] = useState('Filter');
@@ -14,6 +15,8 @@ function DatatableProvider({ children }) {
14
15
  const value = {
15
16
  customButtons,
16
17
  setCustomButtons,
18
+ customPreButtons,
19
+ setCustomPreButtons,
17
20
  filterLabel,
18
21
  setFilterLabel,
19
22
  loading,
@@ -47,6 +47,7 @@ import { styled } from '../Theme';
47
47
  * options?: { searchDebounceTime?: number } & import('mui-datatables').MUIDataTableOptions,
48
48
  * style?: import('react').CSSProperties,
49
49
  * customButtons?: Array<DataTableCustomButton>,
50
+ * customPreButtons?: Array<DataTableCustomButton>,
50
51
  * onChange?: (state: DataTableState, action: string) => void | Promise<void>,
51
52
  * loading?: false | true,
52
53
  * disabled?: false | true,
@@ -166,6 +167,7 @@ function ReDataTable({
166
167
  locale,
167
168
  options, // The options object is usually a property supported by mui-datatable
168
169
  style,
170
+ customPreButtons,
169
171
  customButtons,
170
172
  onChange,
171
173
  loading,
@@ -180,7 +182,7 @@ function ReDataTable({
180
182
  ...rest
181
183
  }) {
182
184
  const oldState = useRef(null);
183
- const { setCustomButtons, setFilterLabel, setLoading, setDisabled } = useDatatableContext();
185
+ const { setCustomPreButtons, setCustomButtons, setFilterLabel, setLoading, setDisabled } = useDatatableContext();
184
186
 
185
187
  const disabledCellStyle = {
186
188
  cursor: 'not-allowed',
@@ -276,6 +278,7 @@ function ReDataTable({
276
278
  });
277
279
 
278
280
  useEffect(() => setCustomButtons(customButtons || []), [customButtons]);
281
+ useEffect(() => setCustomPreButtons(customPreButtons || []), [customPreButtons]);
279
282
  useEffect(() => setLoading(loading), [loading]);
280
283
  useEffect(() => setDisabled(disabled), [disabled]);
281
284
 
@@ -420,6 +423,7 @@ ReDataTable.propTypes = {
420
423
  locale: PropTypes.string,
421
424
  loading: PropTypes.bool,
422
425
  disabled: PropTypes.bool,
426
+ customPreButtons: PropTypes.array,
423
427
  customButtons: PropTypes.array,
424
428
  onChange: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
425
429
  stripped: PropTypes.bool,
@@ -437,6 +441,7 @@ ReDataTable.defaultProps = {
437
441
  locale: 'en',
438
442
  loading: false,
439
443
  disabled: false,
444
+ customPreButtons: [],
440
445
  customButtons: [],
441
446
  onChange: '',
442
447
  stripped: false,
@@ -3,15 +3,15 @@
3
3
  import { createTheme as _createTheme, Components, responsiveFontSizes, type ThemeOptions } from '@mui/material/styles';
4
4
  import { deepmerge } from '@mui/utils';
5
5
  import type { Typography } from '@mui/material/styles/createTypography';
6
- // 为了避免加载全量的字体导致打包后体积太大,目前只选择了 latin 语系的字体
7
- import '@fontsource/inter/latin-300.css';
8
- import '@fontsource/inter/latin-400.css';
9
- import '@fontsource/inter/latin-500.css';
10
- import '@fontsource/inter/latin-700.css';
11
- import '@fontsource/inter/latin-ext-300.css';
12
- import '@fontsource/inter/latin-ext-400.css';
13
- import '@fontsource/inter/latin-ext-500.css';
14
- import '@fontsource/inter/latin-ext-700.css';
6
+ import webfontloader from 'webfontloader';
7
+ // 为了避免加载全量的字体导致打包后体积太大,目前只选择了 MUI 默认的 Roboto 字体
8
+ // eslint-disable-next-line import/no-unresolved
9
+ import '@fontsource/roboto/400';
10
+ // eslint-disable-next-line import/no-unresolved
11
+ import '@fontsource/roboto/500';
12
+ // eslint-disable-next-line import/no-unresolved
13
+ import '@fontsource/roboto/700';
14
+
15
15
  import colors from '../Colors';
16
16
  import { ThemeMode } from '../type';
17
17
  import { cleanedObj } from '../Util';
@@ -48,29 +48,64 @@ declare module '@mui/material/styles/createTypography' {
48
48
  interface TypographyOptions {
49
49
  useNextVariants?: boolean;
50
50
  color?: Record<string, string>;
51
- button?: {
52
- fontWeight?: number;
53
- };
54
51
  }
55
52
  }
56
53
 
57
- const muiDarkTheme = _createTheme({ palette: { mode: 'dark' } });
58
-
59
- const DEFAULT_FONT_FAMILY = [
60
- 'Inter',
61
- 'Avenir',
62
- '-apple-system',
63
- 'BlinkMacSystemFont',
64
- '"Segoe UI"',
65
- 'Roboto',
66
- '"Helvetica Neue"',
67
- 'Arial',
68
- 'sans-serif',
69
- '"Apple Color Emoji"',
70
- '"Segoe UI Emoji"',
71
- '"Segoe UI Symbol"',
72
- ].join(',');
54
+ // 默认深色主题
55
+ const defaultDarkTheme = _createTheme({ palette: { mode: 'dark' } });
56
+
57
+ // 收集字体配置
58
+ export function collectFontFamilies(obj?: { fontFamily?: string }, fontSet: Set<string> = new Set()): Set<string> {
59
+ if (!obj || typeof obj !== 'object') return fontSet;
60
+
61
+ if (typeof obj.fontFamily === 'string') {
62
+ obj.fontFamily
63
+ .replace(/"/g, '')
64
+ .split(',')
65
+ .map((font: string) => font.trim())
66
+ .filter(Boolean)
67
+ .forEach((font: string) => fontSet.add(font));
68
+ }
69
+
70
+ Object.values(obj).forEach((value) => {
71
+ if (typeof value === 'object') {
72
+ collectFontFamilies(value, fontSet);
73
+ }
74
+ });
75
+
76
+ return fontSet;
77
+ }
78
+
79
+ // 动态加载字体
80
+ const prevFonts = new Set<string>(['Roboto', 'inherit']);
81
+ export function loadFonts(fonts: string[]) {
82
+ // 过滤出未加载的字体
83
+ const unloadedFonts = fonts.filter((font) => !prevFonts.has(font));
73
84
 
85
+ // 如果所有字体都已加载,直接返回
86
+ if (unloadedFonts.length === 0) {
87
+ return Promise.resolve(true);
88
+ }
89
+
90
+ // record
91
+ unloadedFonts.forEach((font) => prevFonts.add(font));
92
+
93
+ return new Promise<boolean>((resolve) => {
94
+ webfontloader.load({
95
+ google: {
96
+ families: unloadedFonts,
97
+ },
98
+ active: () => resolve(true),
99
+ inactive: () => resolve(true),
100
+ fontinactive: (familyName, fvd) => {
101
+ prevFonts.delete(familyName);
102
+ console.warn(`font ${familyName} ${fvd} download failed`);
103
+ },
104
+ });
105
+ });
106
+ }
107
+
108
+ // 创建默认主题配置
74
109
  export function createDefaultThemeOptions(mode: ThemeMode = 'light'): ThemeOptions {
75
110
  const result: ThemeOptions = {
76
111
  palette: {
@@ -91,7 +126,6 @@ export function createDefaultThemeOptions(mode: ThemeMode = 'light'): ThemeOptio
91
126
  main: mode === 'light' ? '#222222' : colors.common.white,
92
127
  gray: mode === 'light' ? colors.grey[500] : colors.grey[300],
93
128
  },
94
- fontFamily: DEFAULT_FONT_FAMILY,
95
129
  // button 默认使用粗体
96
130
  button: {
97
131
  fontWeight: 700,
@@ -142,10 +176,11 @@ export function createDefaultThemeOptions(mode: ThemeMode = 'light'): ThemeOptio
142
176
  },
143
177
  },
144
178
  };
179
+
145
180
  // 深色主题
146
181
  if (mode === 'dark') {
147
182
  result.palette = {
148
- ...muiDarkTheme.palette,
183
+ ...defaultDarkTheme.palette,
149
184
  background: {
150
185
  paper: colors.grey[900],
151
186
  default: colors.grey[900],
@@ -215,6 +250,10 @@ export const create = ({
215
250
 
216
251
  const theme = _createTheme(mergedThemeOptions);
217
252
 
253
+ // 异步加载字体
254
+ const fonts = collectFontFamilies(theme.typography);
255
+ loadFonts(Array.from(fonts));
256
+
218
257
  /**
219
258
  * 响应式字体,配置后,theme.typography 会变为下面的结构
220
259
  * {