@particle-network/ui-react 0.4.0-beta.0 → 0.4.0-beta.10

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,20 +1,19 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import "react";
3
+ import { cn } from "@heroui/theme";
3
4
  import { useLang } from "../../hooks/index.js";
4
5
  import { VStack } from "../layout/index.js";
5
6
  import { Text } from "../typography/Text.js";
6
- const ThemeItem = ({ zhName, enName, colorVariables, isSelected, onClick })=>{
7
+ const ThemeItem = ({ id, zhName, enName, isSelected, onClick })=>{
7
8
  const lang = useLang();
8
9
  return /*#__PURE__*/ jsxs(VStack, {
9
10
  center: true,
10
- className: "cursor-pointer hover:scale-105 transition-all duration-300",
11
+ gap: 2,
12
+ className: cn('cursor-pointer hover:scale-105 transition-all duration-300', id),
11
13
  onClick: onClick,
12
14
  children: [
13
15
  /*#__PURE__*/ jsx("div", {
14
- className: "rounded-small border-1.5",
15
- style: {
16
- borderColor: isSelected ? colorVariables.primary : 'transparent'
17
- },
16
+ className: cn('rounded-medium border-2', isSelected ? 'border-primary' : 'border-transparent'),
18
17
  children: /*#__PURE__*/ jsxs("svg", {
19
18
  xmlns: "http://www.w3.org/2000/svg",
20
19
  width: "180",
@@ -29,7 +28,8 @@ const ThemeItem = ({ zhName, enName, colorVariables, isSelected, onClick })=>{
29
28
  width: "116.19",
30
29
  height: "67",
31
30
  rx: "5",
32
- fill: colorVariables.primary
31
+ fill: "currentColor",
32
+ className: "text-primary"
33
33
  }),
34
34
  /*#__PURE__*/ jsx("mask", {
35
35
  id: "mask0_40928_218196",
@@ -47,7 +47,8 @@ const ThemeItem = ({ zhName, enName, colorVariables, isSelected, onClick })=>{
47
47
  width: "116.19",
48
48
  height: "67",
49
49
  rx: "5",
50
- fill: colorVariables.background
50
+ fill: "currentColor",
51
+ className: "text-background"
51
52
  })
52
53
  }),
53
54
  /*#__PURE__*/ jsx("g", {
@@ -58,7 +59,8 @@ const ThemeItem = ({ zhName, enName, colorVariables, isSelected, onClick })=>{
58
59
  width: "116.19",
59
60
  height: "67",
60
61
  rx: "6",
61
- fill: colorVariables.background
62
+ fill: "currentColor",
63
+ className: "text-background"
62
64
  })
63
65
  }),
64
66
  /*#__PURE__*/ jsx("rect", {
@@ -66,7 +68,8 @@ const ThemeItem = ({ zhName, enName, colorVariables, isSelected, onClick })=>{
66
68
  y: "31",
67
69
  width: "100",
68
70
  height: "1",
69
- fill: colorVariables.divider
71
+ fill: "currentColor",
72
+ className: "text-divider"
70
73
  }),
71
74
  /*#__PURE__*/ jsx("rect", {
72
75
  x: "54.082",
@@ -74,7 +77,8 @@ const ThemeItem = ({ zhName, enName, colorVariables, isSelected, onClick })=>{
74
77
  width: "30.0917",
75
78
  height: "6.01835",
76
79
  rx: "3.00917",
77
- fill: colorVariables.foreground
80
+ fill: "currentColor",
81
+ className: "text-foreground"
78
82
  }),
79
83
  /*#__PURE__*/ jsx("rect", {
80
84
  x: "54.082",
@@ -82,7 +86,8 @@ const ThemeItem = ({ zhName, enName, colorVariables, isSelected, onClick })=>{
82
86
  width: "19.5596",
83
87
  height: "6.01835",
84
88
  rx: "3.00917",
85
- fill: colorVariables.bullish
89
+ fill: "currentColor",
90
+ className: "text-bullish"
86
91
  }),
87
92
  /*#__PURE__*/ jsx("rect", {
88
93
  x: "75.8994",
@@ -90,7 +95,8 @@ const ThemeItem = ({ zhName, enName, colorVariables, isSelected, onClick })=>{
90
95
  width: "19.5596",
91
96
  height: "6.01835",
92
97
  rx: "3.00917",
93
- fill: colorVariables.bearish
98
+ fill: "currentColor",
99
+ className: "text-bearish"
94
100
  }),
95
101
  /*#__PURE__*/ jsx("rect", {
96
102
  x: "86.4316",
@@ -98,7 +104,8 @@ const ThemeItem = ({ zhName, enName, colorVariables, isSelected, onClick })=>{
98
104
  width: "6.01835",
99
105
  height: "6.01835",
100
106
  rx: "3.00917",
101
- fill: colorVariables.secondary
107
+ fill: "currentColor",
108
+ className: "text-secondary"
102
109
  }),
103
110
  /*#__PURE__*/ jsx("rect", {
104
111
  x: "94.707",
@@ -106,7 +113,8 @@ const ThemeItem = ({ zhName, enName, colorVariables, isSelected, onClick })=>{
106
113
  width: "6.01835",
107
114
  height: "6.01835",
108
115
  rx: "3.00917",
109
- fill: colorVariables.secondary
116
+ fill: "currentColor",
117
+ className: "text-secondary"
110
118
  }),
111
119
  /*#__PURE__*/ jsx("rect", {
112
120
  x: "102.981",
@@ -114,7 +122,8 @@ const ThemeItem = ({ zhName, enName, colorVariables, isSelected, onClick })=>{
114
122
  width: "6.01835",
115
123
  height: "6.01835",
116
124
  rx: "3.00917",
117
- fill: colorVariables.secondary
125
+ fill: "currentColor",
126
+ className: "text-secondary"
118
127
  }),
119
128
  /*#__PURE__*/ jsx("rect", {
120
129
  x: "27",
@@ -122,7 +131,8 @@ const ThemeItem = ({ zhName, enName, colorVariables, isSelected, onClick })=>{
122
131
  width: "22.5688",
123
132
  height: "22.5688",
124
133
  rx: "11.2844",
125
- fill: colorVariables.primary
134
+ fill: "currentColor",
135
+ className: "text-primary"
126
136
  })
127
137
  ]
128
138
  })
@@ -1,4 +1,7 @@
1
1
  import React from 'react';
2
+ import type { DrawerProps } from '@heroui/drawer';
3
+ export type UXThemeSwitchDrawerProps = Omit<DrawerProps, 'children'>;
4
+ export declare const UXThemeSwitchDrawer: React.FC<UXThemeSwitchDrawerProps>;
2
5
  export interface UXThemeSwitchProps {
3
6
  children?: (onOpen: () => void) => React.ReactNode;
4
7
  }
@@ -2,22 +2,22 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import "react";
3
3
  import { Drawer, DrawerBody, DrawerContent, DrawerFooter, DrawerHeader } from "@heroui/drawer";
4
4
  import { useDisclosure } from "@heroui/use-disclosure";
5
- import ThemeSwitchIcon from "@particle-network/icons/web/ThemeSwitchIcon";
5
+ import { ChartColorSwitchIcon } from "@particle-network/icons/web";
6
6
  import { useI18n } from "../../hooks/index.js";
7
7
  import { Flex, HStack, VStack } from "../layout/index.js";
8
8
  import { Text } from "../typography/Text.js";
9
9
  import { UXButton } from "../UXButton/index.js";
10
10
  import { UXDivider } from "../UXDivider/index.js";
11
11
  import { UXInput } from "../UXInput/index.js";
12
+ import { UXSpinner } from "../UXSpinner/index.js";
12
13
  import { themeData } from "./theme-data.js";
13
14
  import { ThemeItem } from "./theme-item.js";
14
15
  import { useTheme } from "./use-theme.js";
15
- const UXThemeSwitch = ({ children })=>{
16
+ const UXThemeSwitchDrawer = ({ isOpen, onClose, onOpenChange, ...props })=>{
16
17
  const i18n = useI18n();
17
- const { isOpen, onOpen, onClose, onOpenChange } = useDisclosure();
18
- const { selectedTheme, setSelectedTheme, savedTheme, setSavedTheme, selectedFontLink, setSelectedFontLink, savedFontLink, setSavedFontLink } = useTheme();
18
+ const { selectedTheme, setSelectedTheme, savedTheme, setSavedTheme, selectedFontLink, setSelectedFontLink, savedFontLink, setSavedFontLink, fontName, fontLoadStatus, fontLoadError } = useTheme();
19
19
  const handleOpenChange = (_isOpen)=>{
20
- onOpenChange();
20
+ onOpenChange?.(_isOpen);
21
21
  if (!_isOpen) {
22
22
  setSelectedTheme(savedTheme);
23
23
  setSelectedFontLink(savedFontLink);
@@ -26,96 +26,124 @@ const UXThemeSwitch = ({ children })=>{
26
26
  const handleApplyTheme = ()=>{
27
27
  setSavedTheme(selectedTheme);
28
28
  setSavedFontLink(selectedFontLink);
29
- onClose();
29
+ onClose?.();
30
30
  };
31
31
  const handleReset = ()=>{
32
32
  setSelectedTheme(savedTheme);
33
33
  setSelectedFontLink(savedFontLink);
34
34
  };
35
+ return /*#__PURE__*/ jsx(Drawer, {
36
+ isOpen: isOpen,
37
+ backdrop: "transparent",
38
+ onOpenChange: handleOpenChange,
39
+ ...props,
40
+ children: /*#__PURE__*/ jsxs(DrawerContent, {
41
+ children: [
42
+ /*#__PURE__*/ jsx(DrawerHeader, {
43
+ className: "flex flex-col gap-1",
44
+ children: i18n.theme.title
45
+ }),
46
+ /*#__PURE__*/ jsxs(DrawerBody, {
47
+ children: [
48
+ /*#__PURE__*/ jsx(Flex, {
49
+ wrap: true,
50
+ justify: "between",
51
+ className: "gap-y-5",
52
+ children: themeData.map((theme)=>/*#__PURE__*/ jsx(ThemeItem, {
53
+ isSelected: selectedTheme.id === theme.id,
54
+ onClick: ()=>setSelectedTheme(theme),
55
+ ...theme
56
+ }, theme.id))
57
+ }),
58
+ /*#__PURE__*/ jsx(UXDivider, {
59
+ className: "my-lg"
60
+ }),
61
+ /*#__PURE__*/ jsxs(VStack, {
62
+ gap: "md",
63
+ children: [
64
+ /*#__PURE__*/ jsx(Text, {
65
+ body1Bold: true,
66
+ children: i18n.theme.font.title
67
+ }),
68
+ /*#__PURE__*/ jsx(UXInput, {
69
+ isClearable: true,
70
+ startContent: 'loading' === fontLoadStatus && /*#__PURE__*/ jsx(UXSpinner, {
71
+ size: 18
72
+ }),
73
+ isInvalid: 'error' === fontLoadStatus,
74
+ className: "w-full",
75
+ placeholder: i18n.theme.font.placeholder,
76
+ value: selectedFontLink,
77
+ description: /*#__PURE__*/ jsxs(Fragment, {
78
+ children: [
79
+ 'success' === fontLoadStatus && /*#__PURE__*/ jsxs(Text, {
80
+ body3Bold: true,
81
+ color: "success",
82
+ children: [
83
+ i18n.theme.font.success,
84
+ fontName
85
+ ]
86
+ }),
87
+ 'error' === fontLoadStatus && fontLoadError && /*#__PURE__*/ jsx(Text, {
88
+ body3Bold: true,
89
+ color: "danger",
90
+ children: fontLoadError
91
+ })
92
+ ]
93
+ }),
94
+ onValueChange: setSelectedFontLink
95
+ })
96
+ ]
97
+ })
98
+ ]
99
+ }),
100
+ /*#__PURE__*/ jsx(DrawerFooter, {
101
+ className: "mb-8",
102
+ children: /*#__PURE__*/ jsxs(HStack, {
103
+ fullWidth: true,
104
+ gap: "lg",
105
+ children: [
106
+ /*#__PURE__*/ jsx(UXButton, {
107
+ fullWidth: true,
108
+ size: "lg",
109
+ onPress: handleReset,
110
+ children: i18n.theme.reset
111
+ }),
112
+ /*#__PURE__*/ jsx(UXButton, {
113
+ fullWidth: true,
114
+ isDisabled: 'error' === fontLoadStatus,
115
+ size: "lg",
116
+ color: "primary",
117
+ onPress: handleApplyTheme,
118
+ children: i18n.theme.apply
119
+ })
120
+ ]
121
+ })
122
+ })
123
+ ]
124
+ })
125
+ });
126
+ };
127
+ const UXThemeSwitch = ({ children })=>{
128
+ const { isOpen, onOpen, onClose, onOpenChange } = useDisclosure();
35
129
  const renderChildren = ()=>{
36
130
  if (children) return children(onOpen);
37
131
  return /*#__PURE__*/ jsx(UXButton, {
38
132
  isIconOnly: true,
39
133
  variant: "light",
40
134
  onPress: onOpen,
41
- children: /*#__PURE__*/ jsx(ThemeSwitchIcon, {})
135
+ children: /*#__PURE__*/ jsx(ChartColorSwitchIcon, {})
42
136
  });
43
137
  };
44
138
  return /*#__PURE__*/ jsxs(Fragment, {
45
139
  children: [
46
140
  renderChildren(),
47
- /*#__PURE__*/ jsx(Drawer, {
141
+ /*#__PURE__*/ jsx(UXThemeSwitchDrawer, {
48
142
  isOpen: isOpen,
49
- backdrop: "transparent",
50
- onOpenChange: handleOpenChange,
51
- children: /*#__PURE__*/ jsxs(DrawerContent, {
52
- children: [
53
- /*#__PURE__*/ jsx(DrawerHeader, {
54
- className: "flex flex-col gap-1",
55
- children: i18n.theme.title
56
- }),
57
- /*#__PURE__*/ jsxs(DrawerBody, {
58
- children: [
59
- /*#__PURE__*/ jsx(Flex, {
60
- wrap: true,
61
- justify: "between",
62
- className: "gap-y-5",
63
- children: themeData.map((theme)=>/*#__PURE__*/ jsx(ThemeItem, {
64
- isSelected: selectedTheme.key === theme.key,
65
- zhName: theme.zhName,
66
- enName: theme.enName,
67
- colorVariables: theme.colorVariables,
68
- onClick: ()=>setSelectedTheme(theme)
69
- }, theme.key))
70
- }),
71
- /*#__PURE__*/ jsx(UXDivider, {
72
- className: "my-lg"
73
- }),
74
- /*#__PURE__*/ jsxs(VStack, {
75
- gap: "md",
76
- className: "w-full",
77
- children: [
78
- /*#__PURE__*/ jsx(Text, {
79
- className: "text-sm font-medium",
80
- children: i18n.theme.font.title
81
- }),
82
- /*#__PURE__*/ jsx(UXInput, {
83
- isClearable: true,
84
- className: "w-full",
85
- placeholder: i18n.theme.font.placeholder,
86
- value: selectedFontLink,
87
- onValueChange: setSelectedFontLink
88
- })
89
- ]
90
- })
91
- ]
92
- }),
93
- /*#__PURE__*/ jsx(DrawerFooter, {
94
- className: "mb-8",
95
- children: /*#__PURE__*/ jsxs(HStack, {
96
- fullWidth: true,
97
- gap: "lg",
98
- children: [
99
- /*#__PURE__*/ jsx(UXButton, {
100
- fullWidth: true,
101
- size: "lg",
102
- onPress: handleReset,
103
- children: i18n.theme.reset
104
- }),
105
- /*#__PURE__*/ jsx(UXButton, {
106
- fullWidth: true,
107
- size: "lg",
108
- color: "primary",
109
- onPress: handleApplyTheme,
110
- children: i18n.theme.apply
111
- })
112
- ]
113
- })
114
- })
115
- ]
116
- })
143
+ onClose: onClose,
144
+ onOpenChange: onOpenChange
117
145
  })
118
146
  ]
119
147
  });
120
148
  };
121
- export { UXThemeSwitch };
149
+ export { UXThemeSwitch, UXThemeSwitchDrawer };
@@ -0,0 +1,5 @@
1
+ export declare const useColorScheme: () => {
2
+ colorScheme: import("./theme-data").ColorScheme;
3
+ isDark: boolean;
4
+ isLight: boolean;
5
+ };
@@ -0,0 +1,11 @@
1
+ import { useThemeStore } from "./use-theme-store.js";
2
+ const useColorScheme = ()=>{
3
+ const { selectedTheme } = useThemeStore();
4
+ const { colorScheme } = selectedTheme;
5
+ return {
6
+ colorScheme,
7
+ isDark: 'dark' === colorScheme,
8
+ isLight: 'light' === colorScheme
9
+ };
10
+ };
11
+ export { useColorScheme };
@@ -0,0 +1 @@
1
+ export declare const useThemeColor: () => Record<import("@particle-network/ui-shared").UXColor, string>;
@@ -0,0 +1,6 @@
1
+ import { useThemeStore } from "./use-theme-store.js";
2
+ const useThemeColor = ()=>{
3
+ const { selectedTheme } = useThemeStore();
4
+ return selectedTheme.colorVariables;
5
+ };
6
+ export { useThemeColor };
@@ -1,4 +1,5 @@
1
1
  import { type ThemeItemType } from './theme-data';
2
+ export type FontLoadStatus = 'idle' | 'loading' | 'success' | 'error';
2
3
  interface State {
3
4
  /**
4
5
  * 临时选中的 theme(用于预览)
@@ -16,12 +17,27 @@ interface State {
16
17
  * 保存的字体链接
17
18
  */
18
19
  savedFontLink: string;
20
+ /**
21
+ * 应用的字体名称
22
+ */
23
+ fontName: string;
24
+ /**
25
+ * 字体加载状态
26
+ */
27
+ fontLoadStatus: FontLoadStatus;
28
+ /**
29
+ * 字体加载错误信息
30
+ */
31
+ fontLoadError: string | null;
19
32
  }
20
33
  interface Actions {
21
34
  setSelectedTheme: (theme: ThemeItemType) => void;
22
35
  setSavedTheme: (theme: ThemeItemType) => void;
23
36
  setSelectedFontLink: (link: string) => void;
24
37
  setSavedFontLink: (link: string) => void;
38
+ setFontLoadStatus: (status: FontLoadStatus) => void;
39
+ setFontLoadError: (error: string | null) => void;
40
+ setFontName: (name: string) => void;
25
41
  }
26
42
  type ThemeStore = State & Actions;
27
43
  export declare const useThemeStore: import("zustand").UseBoundStore<Omit<import("zustand").StoreApi<ThemeStore>, "setState" | "persist"> & {
@@ -6,6 +6,9 @@ const useThemeStore = create()(persist((set)=>({
6
6
  savedTheme: themeData["0"],
7
7
  selectedFontLink: '',
8
8
  savedFontLink: '',
9
+ fontName: '',
10
+ fontLoadStatus: 'idle',
11
+ fontLoadError: null,
9
12
  setSelectedTheme: (theme)=>set({
10
13
  selectedTheme: theme
11
14
  }),
@@ -17,9 +20,25 @@ const useThemeStore = create()(persist((set)=>({
17
20
  }),
18
21
  setSavedFontLink: (link)=>set({
19
22
  savedFontLink: link
23
+ }),
24
+ setFontLoadStatus: (status)=>set({
25
+ fontLoadStatus: status
26
+ }),
27
+ setFontLoadError: (error)=>set({
28
+ fontLoadError: error
29
+ }),
30
+ setFontName: (name)=>set({
31
+ fontName: name
20
32
  })
21
33
  }), {
22
34
  name: 'ux-theme',
23
- storage: createJSONStorage(()=>'undefined' != typeof window ? window.localStorage : {})
35
+ storage: createJSONStorage(()=>'undefined' != typeof window ? window.localStorage : {}),
36
+ partialize: (state)=>({
37
+ selectedTheme: state.selectedTheme,
38
+ savedTheme: state.savedTheme,
39
+ fontName: state.fontName,
40
+ selectedFontLink: state.selectedFontLink,
41
+ savedFontLink: state.savedFontLink
42
+ })
24
43
  }));
25
44
  export { useThemeStore };
@@ -1,4 +1,5 @@
1
1
  import { type ThemeItemType } from './theme-data';
2
+ import { type FontLoadStatus } from './use-theme-store';
2
3
  /**
3
4
  * UX 主题管理 Hook
4
5
  */
@@ -11,4 +12,7 @@ export declare const useTheme: () => {
11
12
  setSelectedFontLink: (link: string) => void;
12
13
  savedFontLink: string;
13
14
  setSavedFontLink: (link: string) => void;
15
+ fontLoadStatus: FontLoadStatus;
16
+ fontLoadError: string | null;
17
+ fontName: string;
14
18
  };
@@ -1,4 +1,5 @@
1
1
  import { useEffect } from "react";
2
+ import { useDebounceFn } from "ahooks";
2
3
  import { themeKeys } from "./theme-data.js";
3
4
  import { useThemeStore } from "./use-theme-store.js";
4
5
  const DEFAULT_FONT_FAMILY = 'ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji';
@@ -10,7 +11,9 @@ const applyTheme = (theme)=>{
10
11
  themeKeys.forEach((key)=>{
11
12
  root.classList.remove(key);
12
13
  });
13
- root.classList.add(theme.key);
14
+ root.classList.remove('dark');
15
+ root.classList.remove('light');
16
+ root.classList.add(theme.id);
14
17
  };
15
18
  const extractFontFamilyFromLink = (link)=>{
16
19
  if (!link) return null;
@@ -24,38 +27,70 @@ const extractFontFamilyFromLink = (link)=>{
24
27
  return null;
25
28
  }
26
29
  };
27
- const loadGoogleFont = (link)=>{
30
+ const loadGoogleFont = (link, options)=>{
31
+ const { setFontLoadStatus, setFontLoadError, setFontName } = options;
28
32
  if ('undefined' == typeof window) return;
29
33
  const existingLink = document.getElementById('ux-google-font-link');
30
34
  if (existingLink) existingLink.remove();
31
35
  if (!link?.trim()) {
32
36
  document.documentElement.style.setProperty('--ux-font-family', DEFAULT_FONT_FAMILY);
33
37
  document.body.style.fontFamily = DEFAULT_FONT_FAMILY;
38
+ setFontLoadStatus('idle');
39
+ setFontLoadError(null);
40
+ setFontName('');
34
41
  return;
35
42
  }
43
+ setFontLoadStatus('loading');
44
+ setFontLoadError(null);
36
45
  const linkElement = document.createElement('link');
37
46
  linkElement.id = 'ux-google-font-link';
38
47
  linkElement.rel = 'stylesheet';
39
48
  linkElement.href = link;
49
+ linkElement.onload = ()=>{
50
+ setFontLoadStatus('success');
51
+ setFontLoadError(null);
52
+ const fontFamily = extractFontFamilyFromLink(link);
53
+ if (fontFamily) {
54
+ document.documentElement.style.setProperty('--ux-font-family', `"${fontFamily}", ${DEFAULT_FONT_FAMILY}`);
55
+ document.body.style.fontFamily = `"${fontFamily}", ${DEFAULT_FONT_FAMILY}`;
56
+ setFontName(fontFamily);
57
+ }
58
+ };
59
+ linkElement.onerror = ()=>{
60
+ const errorMessage = '字体加载失败,请检查链接是否正确';
61
+ setFontLoadStatus('error');
62
+ setFontLoadError(errorMessage);
63
+ setFontName('');
64
+ document.documentElement.style.setProperty('--ux-font-family', DEFAULT_FONT_FAMILY);
65
+ document.body.style.fontFamily = DEFAULT_FONT_FAMILY;
66
+ };
40
67
  document.head.appendChild(linkElement);
41
- const fontFamily = extractFontFamilyFromLink(link);
42
- if (fontFamily) {
43
- document.documentElement.style.setProperty('--ux-font-family', `"${fontFamily}", ${DEFAULT_FONT_FAMILY}`);
44
- document.body.style.fontFamily = `"${fontFamily}", ${DEFAULT_FONT_FAMILY}`;
45
- }
46
68
  };
47
69
  const useTheme = ()=>{
48
70
  const selectedTheme = useThemeStore((state)=>state.selectedTheme);
49
71
  const savedTheme = useThemeStore((state)=>state.savedTheme);
50
72
  const selectedFontLink = useThemeStore((state)=>state.selectedFontLink);
51
73
  const savedFontLink = useThemeStore((state)=>state.savedFontLink);
74
+ const fontName = useThemeStore((state)=>state.fontName);
75
+ const fontLoadStatus = useThemeStore((state)=>state.fontLoadStatus);
76
+ const fontLoadError = useThemeStore((state)=>state.fontLoadError);
52
77
  const storeSetSelectedTheme = useThemeStore((state)=>state.setSelectedTheme);
53
78
  const storeSetSavedTheme = useThemeStore((state)=>state.setSavedTheme);
54
79
  const storeSetSelectedFontLink = useThemeStore((state)=>state.setSelectedFontLink);
55
80
  const storeSetSavedFontLink = useThemeStore((state)=>state.setSavedFontLink);
81
+ const storeSetFontLoadStatus = useThemeStore((state)=>state.setFontLoadStatus);
82
+ const storeSetFontLoadError = useThemeStore((state)=>state.setFontLoadError);
83
+ const storeSetFontName = useThemeStore((state)=>state.setFontName);
84
+ const { run: debouncedLoadGoogleFont } = useDebounceFn((link)=>loadGoogleFont(link, {
85
+ setFontLoadStatus: storeSetFontLoadStatus,
86
+ setFontLoadError: storeSetFontLoadError,
87
+ setFontName: storeSetFontName
88
+ }), {
89
+ wait: 500
90
+ });
56
91
  useEffect(()=>{
57
92
  applyTheme(savedTheme);
58
- loadGoogleFont(savedFontLink);
93
+ debouncedLoadGoogleFont(savedFontLink);
59
94
  }, [
60
95
  savedTheme,
61
96
  savedFontLink
@@ -70,11 +105,11 @@ const useTheme = ()=>{
70
105
  };
71
106
  const setSelectedFontLink = (link)=>{
72
107
  storeSetSelectedFontLink(link);
73
- loadGoogleFont(link);
108
+ debouncedLoadGoogleFont(link);
74
109
  };
75
110
  const setSavedFontLink = (link)=>{
76
111
  storeSetSavedFontLink(link);
77
- loadGoogleFont(link);
112
+ debouncedLoadGoogleFont(link);
78
113
  };
79
114
  return {
80
115
  selectedTheme,
@@ -84,7 +119,10 @@ const useTheme = ()=>{
84
119
  selectedFontLink,
85
120
  setSelectedFontLink,
86
121
  savedFontLink,
87
- setSavedFontLink
122
+ setSavedFontLink,
123
+ fontLoadStatus,
124
+ fontLoadError,
125
+ fontName
88
126
  };
89
127
  };
90
128
  export { useTheme };
@@ -15,6 +15,9 @@ export declare const useI18n: () => {
15
15
  font: {
16
16
  title: string;
17
17
  placeholder: string;
18
+ loading: string;
19
+ success: string;
20
+ error: string;
18
21
  };
19
22
  };
20
23
  };
@@ -15,7 +15,10 @@ const en = {
15
15
  apply: 'Apply Theme',
16
16
  font: {
17
17
  title: 'Custom Font',
18
- placeholder: 'Enter Google Fonts link'
18
+ placeholder: 'Enter Google Fonts URL or custom font URL',
19
+ loading: 'Loading font...',
20
+ success: 'Font loaded: ',
21
+ error: 'Failed to load font'
19
22
  }
20
23
  }
21
24
  };
@@ -35,7 +38,10 @@ const zh = {
35
38
  apply: '应用主题',
36
39
  font: {
37
40
  title: '自定义字体',
38
- placeholder: '输入 Google Fonts 链接'
41
+ placeholder: '输入 Google Fonts 或自定义字体链接',
42
+ loading: '正在加载字体...',
43
+ success: '字体加载成功:',
44
+ error: '字体加载失败'
39
45
  }
40
46
  }
41
47
  };