@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.
- package/dist/components/ProgressWrapper/index.d.ts +1 -1
- package/dist/components/ProgressWrapper/index.js +18 -3
- package/dist/components/UXDropdown/dropdown-item.js +1 -1
- package/dist/components/UXSelect/index.js +2 -2
- package/dist/components/UXTabs/tabs.classes.js +7 -7
- package/dist/components/UXThemeSwitch/index.d.ts +2 -0
- package/dist/components/UXThemeSwitch/index.js +2 -0
- package/dist/components/UXThemeSwitch/theme-data.d.ts +7 -23
- package/dist/components/UXThemeSwitch/theme-data.js +97 -61
- package/dist/components/UXThemeSwitch/theme-item.d.ts +1 -1
- package/dist/components/UXThemeSwitch/theme-item.js +27 -17
- package/dist/components/UXThemeSwitch/theme-switch.d.ts +3 -0
- package/dist/components/UXThemeSwitch/theme-switch.js +105 -77
- package/dist/components/UXThemeSwitch/use-color-scheme.d.ts +5 -0
- package/dist/components/UXThemeSwitch/use-color-scheme.js +11 -0
- package/dist/components/UXThemeSwitch/use-theme-color.d.ts +1 -0
- package/dist/components/UXThemeSwitch/use-theme-color.js +6 -0
- package/dist/components/UXThemeSwitch/use-theme-store.d.ts +16 -0
- package/dist/components/UXThemeSwitch/use-theme-store.js +20 -1
- package/dist/components/UXThemeSwitch/use-theme.d.ts +4 -0
- package/dist/components/UXThemeSwitch/use-theme.js +49 -11
- package/dist/hooks/useI18n.d.ts +3 -0
- package/dist/hooks/useI18n.js +8 -2
- package/package.json +5 -5
- package/tailwind-preset.js +15 -12
|
@@ -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,
|
|
7
|
+
const ThemeItem = ({ id, zhName, enName, isSelected, onClick })=>{
|
|
7
8
|
const lang = useLang();
|
|
8
9
|
return /*#__PURE__*/ jsxs(VStack, {
|
|
9
10
|
center: true,
|
|
10
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
16
|
+
const UXThemeSwitchDrawer = ({ isOpen, onClose, onOpenChange, ...props })=>{
|
|
16
17
|
const i18n = useI18n();
|
|
17
|
-
const {
|
|
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(
|
|
135
|
+
children: /*#__PURE__*/ jsx(ChartColorSwitchIcon, {})
|
|
42
136
|
});
|
|
43
137
|
};
|
|
44
138
|
return /*#__PURE__*/ jsxs(Fragment, {
|
|
45
139
|
children: [
|
|
46
140
|
renderChildren(),
|
|
47
|
-
/*#__PURE__*/ jsx(
|
|
141
|
+
/*#__PURE__*/ jsx(UXThemeSwitchDrawer, {
|
|
48
142
|
isOpen: isOpen,
|
|
49
|
-
|
|
50
|
-
onOpenChange:
|
|
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,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>;
|
|
@@ -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.
|
|
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
|
-
|
|
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
|
-
|
|
108
|
+
debouncedLoadGoogleFont(link);
|
|
74
109
|
};
|
|
75
110
|
const setSavedFontLink = (link)=>{
|
|
76
111
|
storeSetSavedFontLink(link);
|
|
77
|
-
|
|
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 };
|
package/dist/hooks/useI18n.d.ts
CHANGED
package/dist/hooks/useI18n.js
CHANGED
|
@@ -15,7 +15,10 @@ const en = {
|
|
|
15
15
|
apply: 'Apply Theme',
|
|
16
16
|
font: {
|
|
17
17
|
title: 'Custom Font',
|
|
18
|
-
placeholder: 'Enter Google Fonts
|
|
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
|
};
|