@particle-network/ui-react 0.4.0-beta.3 → 0.4.0-beta.30

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.
Files changed (56) hide show
  1. package/dist/components/ProgressWrapper/index.d.ts +1 -1
  2. package/dist/components/ProgressWrapper/index.js +18 -3
  3. package/dist/components/UXAutocomplete/index.d.ts +5 -0
  4. package/dist/components/UXAutocomplete/index.js +72 -0
  5. package/dist/components/UXButton/button-theme.js +15 -23
  6. package/dist/components/UXButton/use-button.js +2 -1
  7. package/dist/components/UXCheckbox/checkbox.extend.js +3 -3
  8. package/dist/components/UXDatePicker/index.js +1 -1
  9. package/dist/components/UXDateRangePicker/index.js +1 -1
  10. package/dist/components/UXDivider/divider.extend.d.ts +2 -2
  11. package/dist/components/UXDrawer/index.d.ts +9 -0
  12. package/dist/components/UXDrawer/index.js +89 -0
  13. package/dist/components/UXDropdown/dropdown-item.js +1 -1
  14. package/dist/components/UXEmpty/index.d.ts +2 -1
  15. package/dist/components/UXEmpty/index.js +8 -2
  16. package/dist/components/UXHint/index.js +1 -1
  17. package/dist/components/UXInput/index.d.ts +33 -33
  18. package/dist/components/UXInput/input.extend.d.ts +33 -33
  19. package/dist/components/UXModal/index.d.ts +1 -1
  20. package/dist/components/UXModal/index.js +7 -6
  21. package/dist/components/UXSelect/index.js +3 -3
  22. package/dist/components/UXSwitch/index.d.ts +22 -22
  23. package/dist/components/UXSwitch/switch.extend.d.ts +22 -22
  24. package/dist/components/UXTable/index.d.ts +19 -19
  25. package/dist/components/UXTable/table.extend.d.ts +19 -19
  26. package/dist/components/UXTabs/tabs.classes.js +7 -7
  27. package/dist/components/UXThemeSwitch/index.d.ts +0 -1
  28. package/dist/components/UXThemeSwitch/index.js +0 -1
  29. package/dist/components/UXThemeSwitch/theme-item.d.ts +1 -1
  30. package/dist/components/UXThemeSwitch/theme-item.js +4 -5
  31. package/dist/components/UXThemeSwitch/theme-switch.d.ts +3 -3
  32. package/dist/components/UXThemeSwitch/theme-switch.js +131 -101
  33. package/dist/components/UXThemeSwitch/use-color-scheme.d.ts +1 -1
  34. package/dist/components/UXThemeSwitch/use-color-scheme.js +3 -3
  35. package/dist/components/UXThemeSwitch/use-theme-color.d.ts +1 -19
  36. package/dist/components/UXThemeSwitch/use-theme-color.js +3 -3
  37. package/dist/components/UXThemeSwitch/use-theme-store.d.ts +5 -20
  38. package/dist/components/UXThemeSwitch/use-theme-store.js +14 -28
  39. package/dist/components/UXThemeSwitch/use-theme.d.ts +7 -12
  40. package/dist/components/UXThemeSwitch/use-theme.js +73 -80
  41. package/dist/components/UXToast/index.d.ts +7 -4
  42. package/dist/components/UXToast/index.js +21 -17
  43. package/dist/components/UXTooltip/tooltip.extend.d.ts +21 -21
  44. package/dist/components/UXTooltip/tooltip.extend.js +2 -1
  45. package/dist/components/index.d.ts +2 -0
  46. package/dist/components/index.js +2 -0
  47. package/dist/hooks/useI18n.d.ts +6 -2
  48. package/dist/hooks/useI18n.js +15 -7
  49. package/dist/utils/detect.js +1 -2
  50. package/dist/utils/variants.js +16 -16
  51. package/package.json +4 -5
  52. package/tailwind-preset.js +192 -93
  53. package/dist/components/UXThemeSwitch/theme-data.d.ts +0 -29
  54. package/dist/components/UXThemeSwitch/theme-data.js +0 -304
  55. package/dist/icons/index.d.ts +0 -14
  56. package/dist/icons/index.js +0 -120
@@ -1,124 +1,154 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
- import "react";
3
- import { Drawer, DrawerBody, DrawerContent, DrawerFooter, DrawerHeader } from "@heroui/drawer";
2
+ import { useState } from "react";
3
+ import { cn } from "@heroui/theme";
4
4
  import { useDisclosure } from "@heroui/use-disclosure";
5
- import { ThemeSwitchIcon } from "@particle-network/icons/web";
5
+ import { ChartColorSwitchIcon, CopyIcon } from "@particle-network/icons/web";
6
+ import { themeData } from "@particle-network/ui-shared";
6
7
  import { useI18n } from "../../hooks/index.js";
7
- import { Flex, HStack, VStack } from "../layout/index.js";
8
+ import { UXCopy, UXTooltip } from "../index.js";
9
+ import { HStack, VStack } from "../layout/index.js";
8
10
  import { Text } from "../typography/Text.js";
9
11
  import { UXButton } from "../UXButton/index.js";
10
12
  import { UXDivider } from "../UXDivider/index.js";
11
13
  import { UXInput } from "../UXInput/index.js";
14
+ import { UXModal } from "../UXModal/index.js";
12
15
  import { UXSpinner } from "../UXSpinner/index.js";
13
- import { themeData } from "./theme-data.js";
14
16
  import { ThemeItem } from "./theme-item.js";
15
17
  import { useTheme } from "./use-theme.js";
16
- const UXThemeSwitchDrawer = ({ isOpen, onClose, onOpenChange, ...props })=>{
18
+ const FONT_EXAMPLES = [
19
+ {
20
+ title: 'Manrope',
21
+ url: 'https://fonts.googleapis.com/css2?family=Manrope:wght@200..800&display=swap'
22
+ },
23
+ {
24
+ title: 'Poppins',
25
+ url: 'https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap'
26
+ },
27
+ {
28
+ title: 'Roboto',
29
+ url: 'https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100..900;1,100..900&display=swap'
30
+ }
31
+ ];
32
+ const UXThemeSwitchModal = ({ isOpen, onClose, onOpenChange, ...props })=>{
17
33
  const i18n = useI18n();
18
- const { selectedTheme, setSelectedTheme, savedTheme, setSavedTheme, selectedFontLink, setSelectedFontLink, savedFontLink, setSavedFontLink, fontName, fontLoadStatus, fontLoadError } = useTheme();
19
- const handleOpenChange = (_isOpen)=>{
20
- onOpenChange?.(_isOpen);
21
- if (!_isOpen) {
22
- setSelectedTheme(savedTheme);
23
- setSelectedFontLink(savedFontLink);
24
- }
25
- };
26
- const handleApplyTheme = ()=>{
27
- setSavedTheme(selectedTheme);
28
- setSavedFontLink(selectedFontLink);
29
- onClose?.();
30
- };
31
- const handleReset = ()=>{
32
- setSelectedTheme(savedTheme);
33
- setSelectedFontLink(savedFontLink);
34
- };
35
- return /*#__PURE__*/ jsx(Drawer, {
34
+ const { theme: selectedTheme, setTheme, fontUrl, setFontUrl, fontName, fontLoadStatus, clearFontUrl } = useTheme();
35
+ const [isFontExampleOpen, setIsFontExampleOpen] = useState(false);
36
+ return /*#__PURE__*/ jsx(UXModal, {
36
37
  isOpen: isOpen,
37
- backdrop: "transparent",
38
- onOpenChange: handleOpenChange,
38
+ title: i18n.theme.title,
39
+ footer: /*#__PURE__*/ jsx(UXButton, {
40
+ fullWidth: true,
41
+ size: "lg",
42
+ color: "primary",
43
+ onPress: onClose,
44
+ children: i18n.theme.done
45
+ }),
46
+ onOpenChange: onOpenChange,
39
47
  ...props,
40
- children: /*#__PURE__*/ jsxs(DrawerContent, {
48
+ children: /*#__PURE__*/ jsxs(VStack, {
49
+ gap: "lg",
41
50
  children: [
42
- /*#__PURE__*/ jsx(DrawerHeader, {
43
- className: "flex flex-col gap-1",
44
- children: i18n.theme.title
51
+ /*#__PURE__*/ jsx("div", {
52
+ className: "grid grid-cols-3 gap-y-5 pt-1",
53
+ children: themeData.map((theme)=>/*#__PURE__*/ jsx(ThemeItem, {
54
+ isSelected: selectedTheme.id === theme.id,
55
+ onClick: ()=>setTheme(theme),
56
+ ...theme
57
+ }, theme.id))
45
58
  }),
46
- /*#__PURE__*/ jsxs(DrawerBody, {
59
+ /*#__PURE__*/ jsx(UXDivider, {}),
60
+ /*#__PURE__*/ jsxs(VStack, {
61
+ gap: "md",
47
62
  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))
63
+ /*#__PURE__*/ jsx(Text, {
64
+ body1Bold: true,
65
+ children: i18n.theme.font.title
57
66
  }),
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, {
67
+ /*#__PURE__*/ jsx(UXInput, {
68
+ isClearable: true,
69
+ startContent: 'loading' === fontLoadStatus && /*#__PURE__*/ jsx(UXSpinner, {
70
+ size: 18
71
+ }),
72
+ isInvalid: 'error' === fontLoadStatus,
73
+ className: "w-full",
74
+ placeholder: i18n.theme.font.placeholder,
75
+ value: fontUrl,
76
+ description: /*#__PURE__*/ jsxs(Fragment, {
77
+ children: [
78
+ 'success' === fontLoadStatus && /*#__PURE__*/ jsxs(Text, {
79
+ body3Bold: true,
80
+ color: "success",
78
81
  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
- })
82
+ i18n.theme.font.success,
83
+ fontName
92
84
  ]
93
85
  }),
94
- onValueChange: setSelectedFontLink
95
- })
96
- ]
86
+ 'error' === fontLoadStatus && /*#__PURE__*/ jsx(Text, {
87
+ body3Bold: true,
88
+ color: "danger",
89
+ children: i18n.theme.font.error
90
+ })
91
+ ]
92
+ }),
93
+ onValueChange: setFontUrl,
94
+ onClear: clearFontUrl
97
95
  })
98
96
  ]
99
97
  }),
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
- })
98
+ /*#__PURE__*/ jsxs(Text, {
99
+ color: "secondary",
100
+ children: [
101
+ i18n.theme.font.hint,
102
+ /*#__PURE__*/ jsx("a", {
103
+ href: "https://fonts.google.com/",
104
+ target: "_blank",
105
+ rel: "noopener noreferrer",
106
+ className: "hover:underline",
107
+ children: "https://fonts.google.com/"
108
+ })
109
+ ]
110
+ }),
111
+ /*#__PURE__*/ jsx(UXButton, {
112
+ className: "self-start",
113
+ variant: "text",
114
+ color: "primary",
115
+ onPress: ()=>{
116
+ setIsFontExampleOpen((prev)=>!prev);
117
+ },
118
+ children: i18n.theme.font.example[isFontExampleOpen ? 'hide' : 'show']
119
+ }),
120
+ /*#__PURE__*/ jsx(VStack, {
121
+ gap: "sm",
122
+ className: cn(isFontExampleOpen ? 'flex' : 'hidden'),
123
+ children: FONT_EXAMPLES.map((example)=>/*#__PURE__*/ jsxs(HStack, {
124
+ gap: "lg",
125
+ children: [
126
+ /*#__PURE__*/ jsx(UXTooltip, {
127
+ content: example.url,
128
+ placement: "bottom",
129
+ children: /*#__PURE__*/ jsxs(Text, {
130
+ color: "secondary",
131
+ className: "flex-1 truncate cursor-pointer",
132
+ children: [
133
+ example.title,
134
+ ": ",
135
+ example.url
136
+ ]
137
+ })
138
+ }),
139
+ /*#__PURE__*/ jsx(UXCopy, {
140
+ copyText: example.url,
141
+ children: /*#__PURE__*/ jsx(UXButton, {
142
+ isIconOnly: true,
143
+ size: "sm",
144
+ variant: "light",
145
+ children: /*#__PURE__*/ jsx(CopyIcon, {
146
+ color: "secondary"
147
+ })
148
+ })
149
+ })
150
+ ]
151
+ }, example.title))
122
152
  })
123
153
  ]
124
154
  })
@@ -132,13 +162,13 @@ const UXThemeSwitch = ({ children })=>{
132
162
  isIconOnly: true,
133
163
  variant: "light",
134
164
  onPress: onOpen,
135
- children: /*#__PURE__*/ jsx(ThemeSwitchIcon, {})
165
+ children: /*#__PURE__*/ jsx(ChartColorSwitchIcon, {})
136
166
  });
137
167
  };
138
168
  return /*#__PURE__*/ jsxs(Fragment, {
139
169
  children: [
140
170
  renderChildren(),
141
- /*#__PURE__*/ jsx(UXThemeSwitchDrawer, {
171
+ /*#__PURE__*/ jsx(UXThemeSwitchModal, {
142
172
  isOpen: isOpen,
143
173
  onClose: onClose,
144
174
  onOpenChange: onOpenChange
@@ -146,4 +176,4 @@ const UXThemeSwitch = ({ children })=>{
146
176
  ]
147
177
  });
148
178
  };
149
- export { UXThemeSwitch, UXThemeSwitchDrawer };
179
+ export { UXThemeSwitch, UXThemeSwitchModal };
@@ -1,5 +1,5 @@
1
1
  export declare const useColorScheme: () => {
2
- colorScheme: import("./theme-data").ColorScheme;
2
+ colorScheme: import("@particle-network/ui-shared").ColorScheme;
3
3
  isDark: boolean;
4
4
  isLight: boolean;
5
5
  };
@@ -1,7 +1,7 @@
1
- import { useThemeStore } from "./use-theme-store.js";
1
+ import { useTheme } from "./use-theme.js";
2
2
  const useColorScheme = ()=>{
3
- const { selectedTheme } = useThemeStore();
4
- const { colorScheme } = selectedTheme;
3
+ const { theme } = useTheme();
4
+ const { colorScheme } = theme;
5
5
  return {
6
6
  colorScheme,
7
7
  isDark: 'dark' === colorScheme,
@@ -1,19 +1 @@
1
- export declare const useThemeColor: () => {
2
- foreground: string;
3
- secondary: string;
4
- tertiary: string;
5
- primary: string;
6
- success: string;
7
- danger: string;
8
- alert: string;
9
- warning: string;
10
- gold: string;
11
- background: string;
12
- 'background-200': string;
13
- 'background-300': string;
14
- 'background-400': string;
15
- overlay: string;
16
- divider: string;
17
- bullish: string;
18
- bearish: string;
19
- };
1
+ export declare const useThemeColor: () => Record<import("@particle-network/ui-shared").UXColor, string>;
@@ -1,6 +1,6 @@
1
- import { useThemeStore } from "./use-theme-store.js";
1
+ import { useTheme } from "./use-theme.js";
2
2
  const useThemeColor = ()=>{
3
- const { selectedTheme } = useThemeStore();
4
- return selectedTheme.colorVariables;
3
+ const { theme } = useTheme();
4
+ return theme.colorVariables;
5
5
  };
6
6
  export { useThemeColor };
@@ -1,22 +1,14 @@
1
- import { type ThemeItemType } from './theme-data';
1
+ import { type ThemeItemType } from '@particle-network/ui-shared';
2
2
  export type FontLoadStatus = 'idle' | 'loading' | 'success' | 'error';
3
3
  interface State {
4
- /**
5
- * 临时选中的 theme(用于预览)
6
- */
7
- selectedTheme: ThemeItemType;
8
4
  /**
9
5
  * 保存的主题
10
6
  */
11
- savedTheme: ThemeItemType;
12
- /**
13
- * 临时输入的字体链接(用于预览)
14
- */
15
- selectedFontLink: string;
7
+ theme: ThemeItemType;
16
8
  /**
17
9
  * 保存的字体链接
18
10
  */
19
- savedFontLink: string;
11
+ fontUrl: string;
20
12
  /**
21
13
  * 应用的字体名称
22
14
  */
@@ -25,18 +17,11 @@ interface State {
25
17
  * 字体加载状态
26
18
  */
27
19
  fontLoadStatus: FontLoadStatus;
28
- /**
29
- * 字体加载错误信息
30
- */
31
- fontLoadError: string | null;
32
20
  }
33
21
  interface Actions {
34
- setSelectedTheme: (theme: ThemeItemType) => void;
35
- setSavedTheme: (theme: ThemeItemType) => void;
36
- setSelectedFontLink: (link: string) => void;
37
- setSavedFontLink: (link: string) => void;
22
+ setTheme: (theme: ThemeItemType) => void;
23
+ setFontUrl: (fontUrl: string) => void;
38
24
  setFontLoadStatus: (status: FontLoadStatus) => void;
39
- setFontLoadError: (error: string | null) => void;
40
25
  setFontName: (name: string) => void;
41
26
  }
42
27
  type ThemeStore = State & Actions;
@@ -1,44 +1,30 @@
1
+ import { themeData } from "@particle-network/ui-shared";
1
2
  import { create } from "zustand";
2
3
  import { createJSONStorage, persist } from "zustand/middleware";
3
- import { themeData } from "./theme-data.js";
4
4
  const useThemeStore = create()(persist((set)=>({
5
- selectedTheme: themeData["0"],
6
- savedTheme: themeData["0"],
7
- selectedFontLink: '',
8
- savedFontLink: '',
5
+ theme: themeData["0"],
6
+ fontUrl: '',
9
7
  fontName: '',
10
8
  fontLoadStatus: 'idle',
11
- fontLoadError: null,
12
- setSelectedTheme: (theme)=>set({
13
- selectedTheme: theme
9
+ setTheme: (theme)=>set({
10
+ theme
14
11
  }),
15
- setSavedTheme: (theme)=>set({
16
- savedTheme: theme
12
+ setFontUrl: (fontUrl)=>set({
13
+ fontUrl
17
14
  }),
18
- setSelectedFontLink: (link)=>set({
19
- selectedFontLink: link
15
+ setFontLoadStatus: (fontLoadStatus)=>set({
16
+ fontLoadStatus
20
17
  }),
21
- setSavedFontLink: (link)=>set({
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
18
+ setFontName: (fontName)=>set({
19
+ fontName
32
20
  })
33
21
  }), {
34
- name: 'ux-theme',
22
+ name: 'ux-theme-2',
35
23
  storage: createJSONStorage(()=>'undefined' != typeof window ? window.localStorage : {}),
36
24
  partialize: (state)=>({
37
- selectedTheme: state.selectedTheme,
38
- savedTheme: state.savedTheme,
25
+ theme: state.theme,
39
26
  fontName: state.fontName,
40
- selectedFontLink: state.selectedFontLink,
41
- savedFontLink: state.savedFontLink
27
+ fontUrl: state.fontUrl
42
28
  })
43
29
  }));
44
30
  export { useThemeStore };
@@ -1,18 +1,13 @@
1
- import { type ThemeItemType } from './theme-data';
2
- import { type FontLoadStatus } from './use-theme-store';
1
+ import { type ThemeItemType } from '@particle-network/ui-shared';
3
2
  /**
4
3
  * UX 主题管理 Hook
5
4
  */
6
5
  export declare const useTheme: () => {
7
- selectedTheme: ThemeItemType;
8
- setSelectedTheme: (theme: ThemeItemType) => void;
9
- savedTheme: ThemeItemType;
10
- setSavedTheme: (theme: ThemeItemType) => void;
11
- selectedFontLink: string;
12
- setSelectedFontLink: (link: string) => void;
13
- savedFontLink: string;
14
- setSavedFontLink: (link: string) => void;
15
- fontLoadStatus: FontLoadStatus;
16
- fontLoadError: string | null;
6
+ theme: ThemeItemType;
7
+ setTheme: (theme: ThemeItemType) => void;
8
+ fontUrl: string;
9
+ setFontUrl: (link: string) => void;
10
+ clearFontUrl: () => void;
11
+ fontLoadStatus: import("./use-theme-store").FontLoadStatus;
17
12
  fontName: string;
18
13
  };
@@ -1,8 +1,19 @@
1
1
  import { useEffect } from "react";
2
+ import { themeKeys } from "@particle-network/ui-shared";
2
3
  import { useDebounceFn } from "ahooks";
3
- import { themeKeys } from "./theme-data.js";
4
4
  import { useThemeStore } from "./use-theme-store.js";
5
- const DEFAULT_FONT_FAMILY = 'ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji';
5
+ const DEFAULT_FONT_FAMILY = 'Inter,system-ui,sans-serif,"Microsoft YaHei"';
6
+ const PRELOAD_FONTS_URL = 'https://fonts.googleapis.com/css2?family=Geist:wght@100..900&family=IBM+Plex+Sans:ital,wght@0,100..700;1,100..700&display=swap';
7
+ const preloadFonts = ()=>{
8
+ if ('undefined' == typeof window) return;
9
+ const existingLink = document.getElementById('ux-preload-fonts-link');
10
+ if (existingLink) return;
11
+ const linkElement = document.createElement('link');
12
+ linkElement.id = 'ux-preload-fonts-link';
13
+ linkElement.rel = 'stylesheet';
14
+ linkElement.href = PRELOAD_FONTS_URL;
15
+ document.head.appendChild(linkElement);
16
+ };
6
17
  const applyTheme = (theme)=>{
7
18
  if ('undefined' == typeof window) return;
8
19
  const root = document.documentElement;
@@ -11,6 +22,8 @@ const applyTheme = (theme)=>{
11
22
  themeKeys.forEach((key)=>{
12
23
  root.classList.remove(key);
13
24
  });
25
+ root.classList.remove('dark');
26
+ root.classList.remove('light');
14
27
  root.classList.add(theme.id);
15
28
  };
16
29
  const extractFontFamilyFromLink = (link)=>{
@@ -19,107 +32,87 @@ const extractFontFamilyFromLink = (link)=>{
19
32
  const url = new URL(link);
20
33
  const familyParam = url.searchParams.get('family');
21
34
  if (!familyParam) return null;
22
- const fontName = familyParam.split(':')[0].replace(/\+/g, ' ');
23
- return fontName;
35
+ const firstFamily = familyParam.split('&family=')[0].split(':')[0].replace(/\+/g, ' ');
36
+ return firstFamily;
24
37
  } catch {
25
38
  return null;
26
39
  }
27
40
  };
28
- const loadGoogleFont = (link, options)=>{
29
- const { setFontLoadStatus, setFontLoadError, setFontName } = options;
41
+ const applyFont = (fontName)=>{
30
42
  if ('undefined' == typeof window) return;
31
- const existingLink = document.getElementById('ux-google-font-link');
32
- if (existingLink) existingLink.remove();
33
- if (!link?.trim()) {
43
+ if (fontName) {
44
+ document.documentElement.style.setProperty('--ux-font-family', `"${fontName}", ${DEFAULT_FONT_FAMILY}`);
45
+ document.body.style.fontFamily = `"${fontName}", ${DEFAULT_FONT_FAMILY}`;
46
+ } else {
34
47
  document.documentElement.style.setProperty('--ux-font-family', DEFAULT_FONT_FAMILY);
35
48
  document.body.style.fontFamily = DEFAULT_FONT_FAMILY;
36
- setFontLoadStatus('idle');
37
- setFontLoadError(null);
38
- setFontName('');
39
- return;
40
49
  }
41
- setFontLoadStatus('loading');
42
- setFontLoadError(null);
43
- const linkElement = document.createElement('link');
44
- linkElement.id = 'ux-google-font-link';
45
- linkElement.rel = 'stylesheet';
46
- linkElement.href = link;
47
- linkElement.onload = ()=>{
48
- setFontLoadStatus('success');
49
- setFontLoadError(null);
50
- const fontFamily = extractFontFamilyFromLink(link);
51
- if (fontFamily) {
52
- document.documentElement.style.setProperty('--ux-font-family', `"${fontFamily}", ${DEFAULT_FONT_FAMILY}`);
53
- document.body.style.fontFamily = `"${fontFamily}", ${DEFAULT_FONT_FAMILY}`;
54
- setFontName(fontFamily);
55
- }
56
- };
57
- linkElement.onerror = ()=>{
58
- const errorMessage = '字体加载失败,请检查链接是否正确';
59
- setFontLoadStatus('error');
60
- setFontLoadError(errorMessage);
61
- setFontName('');
62
- document.documentElement.style.setProperty('--ux-font-family', DEFAULT_FONT_FAMILY);
63
- document.body.style.fontFamily = DEFAULT_FONT_FAMILY;
64
- };
65
- document.head.appendChild(linkElement);
66
50
  };
67
51
  const useTheme = ()=>{
68
- const selectedTheme = useThemeStore((state)=>state.selectedTheme);
69
- const savedTheme = useThemeStore((state)=>state.savedTheme);
70
- const selectedFontLink = useThemeStore((state)=>state.selectedFontLink);
71
- const savedFontLink = useThemeStore((state)=>state.savedFontLink);
52
+ const theme = useThemeStore((state)=>state.theme);
53
+ const fontUrl = useThemeStore((state)=>state.fontUrl);
72
54
  const fontName = useThemeStore((state)=>state.fontName);
73
55
  const fontLoadStatus = useThemeStore((state)=>state.fontLoadStatus);
74
- const fontLoadError = useThemeStore((state)=>state.fontLoadError);
75
- const storeSetSelectedTheme = useThemeStore((state)=>state.setSelectedTheme);
76
- const storeSetSavedTheme = useThemeStore((state)=>state.setSavedTheme);
77
- const storeSetSelectedFontLink = useThemeStore((state)=>state.setSelectedFontLink);
78
- const storeSetSavedFontLink = useThemeStore((state)=>state.setSavedFontLink);
56
+ const storeSetTheme = useThemeStore((state)=>state.setTheme);
57
+ const storeSetFontUrl = useThemeStore((state)=>state.setFontUrl);
79
58
  const storeSetFontLoadStatus = useThemeStore((state)=>state.setFontLoadStatus);
80
- const storeSetFontLoadError = useThemeStore((state)=>state.setFontLoadError);
81
59
  const storeSetFontName = useThemeStore((state)=>state.setFontName);
82
- const { run: debouncedLoadGoogleFont } = useDebounceFn((link)=>loadGoogleFont(link, {
83
- setFontLoadStatus: storeSetFontLoadStatus,
84
- setFontLoadError: storeSetFontLoadError,
85
- setFontName: storeSetFontName
86
- }), {
87
- wait: 500
60
+ const applyFontWithStatus = (customFontUrl, themeFontName)=>{
61
+ if ('undefined' == typeof window) return;
62
+ const url = customFontUrl?.trim();
63
+ if (!url) return void applyFont(themeFontName);
64
+ const existingLink = document.getElementById('ux-google-font-link');
65
+ if (existingLink) existingLink.remove();
66
+ storeSetFontLoadStatus('loading');
67
+ const linkElement = document.createElement('link');
68
+ linkElement.id = 'ux-google-font-link';
69
+ linkElement.rel = 'stylesheet';
70
+ linkElement.href = url;
71
+ linkElement.onload = ()=>{
72
+ storeSetFontLoadStatus('success');
73
+ const fontFamily = extractFontFamilyFromLink(url);
74
+ if (fontFamily) {
75
+ applyFont(fontFamily);
76
+ storeSetFontName(fontFamily);
77
+ }
78
+ };
79
+ linkElement.onerror = ()=>{
80
+ storeSetFontLoadStatus('error');
81
+ storeSetFontName('');
82
+ applyFont(themeFontName);
83
+ };
84
+ document.head.appendChild(linkElement);
85
+ };
86
+ const { run: debouncedApplyFont } = useDebounceFn((customFontLink, themeFontName)=>applyFontWithStatus(customFontLink, themeFontName), {
87
+ wait: 300
88
88
  });
89
89
  useEffect(()=>{
90
- applyTheme(savedTheme);
91
- debouncedLoadGoogleFont(savedFontLink);
92
- }, [
93
- savedTheme,
94
- savedFontLink
95
- ]);
96
- const setSelectedTheme = (theme)=>{
97
- storeSetSelectedTheme(theme);
90
+ preloadFonts();
98
91
  applyTheme(theme);
99
- };
100
- const setSavedTheme = (theme)=>{
101
- storeSetSavedTheme(theme);
92
+ debouncedApplyFont(fontUrl, theme.fontName);
93
+ }, []);
94
+ const setTheme = (theme)=>{
95
+ storeSetTheme(theme);
102
96
  applyTheme(theme);
97
+ debouncedApplyFont(fontUrl, theme.fontName);
103
98
  };
104
- const setSelectedFontLink = (link)=>{
105
- storeSetSelectedFontLink(link);
106
- debouncedLoadGoogleFont(link);
99
+ const setFontUrl = (link)=>{
100
+ storeSetFontUrl(link);
101
+ debouncedApplyFont(link, theme.fontName);
107
102
  };
108
- const setSavedFontLink = (link)=>{
109
- storeSetSavedFontLink(link);
110
- debouncedLoadGoogleFont(link);
103
+ const clearFontUrl = ()=>{
104
+ storeSetFontUrl('');
105
+ storeSetFontName('');
106
+ storeSetFontLoadStatus('idle');
107
+ debouncedApplyFont('', theme.fontName);
111
108
  };
112
109
  return {
113
- selectedTheme,
114
- setSelectedTheme,
115
- savedTheme,
116
- setSavedTheme,
117
- selectedFontLink,
118
- setSelectedFontLink,
119
- savedFontLink,
120
- setSavedFontLink,
110
+ theme,
111
+ setTheme,
112
+ fontUrl,
113
+ setFontUrl,
114
+ clearFontUrl,
121
115
  fontLoadStatus,
122
- fontLoadError,
123
116
  fontName
124
117
  };
125
118
  };