@particle-network/ui-react 0.4.0-beta.24 → 0.4.0-beta.25

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.
@@ -288,6 +288,7 @@ export declare const UXInput: React.ForwardRefExoticComponent<Omit<Omit<{
288
288
  list?: string | undefined;
289
289
  step?: string | number | undefined;
290
290
  size?: "sm" | "md" | "lg" | undefined;
291
+ label?: React.ReactNode;
291
292
  value?: string | (readonly string[] & string) | undefined;
292
293
  width?: string | number | undefined;
293
294
  height?: string | number | undefined;
@@ -296,7 +297,7 @@ export declare const UXInput: React.ForwardRefExoticComponent<Omit<Omit<{
296
297
  disabled?: boolean | undefined;
297
298
  variant?: "flat" | "bordered" | "faded" | "underlined" | undefined;
298
299
  isClearable?: boolean | undefined;
299
- classNames?: import("@heroui/theme").SlotsToClasses<"description" | "errorMessage" | "label" | "base" | "input" | "clearButton" | "mainWrapper" | "inputWrapper" | "innerWrapper" | "helperWrapper"> | undefined;
300
+ classNames?: import("@heroui/theme").SlotsToClasses<"base" | "input" | "label" | "description" | "errorMessage" | "clearButton" | "mainWrapper" | "inputWrapper" | "innerWrapper" | "helperWrapper"> | undefined;
300
301
  isDisabled?: boolean | undefined;
301
302
  isReadOnly?: boolean | undefined;
302
303
  isRequired?: boolean | undefined;
@@ -308,7 +309,6 @@ export declare const UXInput: React.ForwardRefExoticComponent<Omit<Omit<{
308
309
  errorMessage?: React.ReactNode | ((v: import("@react-types/shared").ValidationResult) => React.ReactNode);
309
310
  onFocusChange?: ((isFocused: boolean) => void) | undefined;
310
311
  placeholder?: string | undefined;
311
- label?: React.ReactNode;
312
312
  excludeFromTabOrder?: boolean | undefined;
313
313
  autoComplete?: string | undefined;
314
314
  maxLength?: number | undefined;
@@ -285,6 +285,7 @@ declare const ExtendedInput: import("react").ForwardRefExoticComponent<Omit<{
285
285
  list?: string | undefined;
286
286
  step?: string | number | undefined;
287
287
  size?: "sm" | "md" | "lg" | undefined;
288
+ label?: import("react").ReactNode;
288
289
  value?: string | (readonly string[] & string) | undefined;
289
290
  width?: string | number | undefined;
290
291
  height?: string | number | undefined;
@@ -293,7 +294,7 @@ declare const ExtendedInput: import("react").ForwardRefExoticComponent<Omit<{
293
294
  disabled?: boolean | undefined;
294
295
  variant?: "flat" | "bordered" | "faded" | "underlined" | undefined;
295
296
  isClearable?: boolean | undefined;
296
- classNames?: import("@heroui/theme").SlotsToClasses<"description" | "errorMessage" | "label" | "base" | "input" | "clearButton" | "mainWrapper" | "inputWrapper" | "innerWrapper" | "helperWrapper"> | undefined;
297
+ classNames?: import("@heroui/theme").SlotsToClasses<"base" | "input" | "label" | "description" | "errorMessage" | "clearButton" | "mainWrapper" | "inputWrapper" | "innerWrapper" | "helperWrapper"> | undefined;
297
298
  isDisabled?: boolean | undefined;
298
299
  isReadOnly?: boolean | undefined;
299
300
  isRequired?: boolean | undefined;
@@ -305,7 +306,6 @@ declare const ExtendedInput: import("react").ForwardRefExoticComponent<Omit<{
305
306
  errorMessage?: import("react").ReactNode | ((v: import("@react-types/shared").ValidationResult) => import("react").ReactNode);
306
307
  onFocusChange?: ((isFocused: boolean) => void) | undefined;
307
308
  placeholder?: string | undefined;
308
- label?: import("react").ReactNode;
309
309
  excludeFromTabOrder?: boolean | undefined;
310
310
  autoComplete?: string | undefined;
311
311
  maxLength?: number | undefined;
@@ -6,4 +6,4 @@ export type UXModalProps = Omit<ModalProps, 'closeButton'> & {
6
6
  tip?: React.ReactNode;
7
7
  titleAlign?: 'left' | 'center';
8
8
  };
9
- export declare const UXModal: React.ForwardRefExoticComponent<Omit<UXModalProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
9
+ export declare const UXModal: React.FC<UXModalProps>;
@@ -1,14 +1,13 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
- import { forwardRef } from "react";
2
+ import "react";
3
3
  import { Modal, ModalBody, ModalContent, ModalFooter, ModalHeader } from "@heroui/modal";
4
4
  import CloseIcon from "@particle-network/icons/web/CloseIcon";
5
5
  import { Center, Circle, Flex } from "../layout/index.js";
6
6
  import { Text } from "../typography/Text.js";
7
7
  import { UXButton } from "../UXButton/index.js";
8
- const UXModal = /*#__PURE__*/ forwardRef((props, ref)=>{
8
+ const UXModal = (props)=>{
9
9
  const { title, footer, backdrop, scrollBehavior = 'inside', children, titleAlign = 'left', classNames, tip, ...restProps } = props;
10
10
  return /*#__PURE__*/ jsx(Modal, {
11
- ref: ref,
12
11
  backdrop: backdrop,
13
12
  scrollBehavior: scrollBehavior,
14
13
  classNames: {
@@ -88,6 +87,6 @@ const UXModal = /*#__PURE__*/ forwardRef((props, ref)=>{
88
87
  ]
89
88
  })
90
89
  });
91
- });
90
+ };
92
91
  UXModal.displayName = 'UX.Modal';
93
92
  export { UXModal };
@@ -292,7 +292,7 @@ export declare const UXSwitch: React.ForwardRefExoticComponent<Omit<Omit<{
292
292
  height?: string | number | undefined;
293
293
  multiple?: boolean | undefined;
294
294
  disabled?: boolean | undefined;
295
- classNames?: import("@heroui/theme").SlotsToClasses<"label" | "base" | "startContent" | "endContent" | "wrapper" | "hiddenInput" | "thumb" | "thumbIcon"> | undefined;
295
+ classNames?: import("@heroui/theme").SlotsToClasses<"base" | "label" | "startContent" | "endContent" | "wrapper" | "hiddenInput" | "thumb" | "thumbIcon"> | undefined;
296
296
  isDisabled?: boolean | undefined;
297
297
  isReadOnly?: boolean | undefined;
298
298
  onFocusChange?: ((isFocused: boolean) => void) | undefined;
@@ -289,7 +289,7 @@ declare const ExtendedSwitch: import("react").ForwardRefExoticComponent<Omit<{
289
289
  height?: string | number | undefined;
290
290
  multiple?: boolean | undefined;
291
291
  disabled?: boolean | undefined;
292
- classNames?: import("@heroui/theme").SlotsToClasses<"label" | "base" | "startContent" | "endContent" | "wrapper" | "hiddenInput" | "thumb" | "thumbIcon"> | undefined;
292
+ classNames?: import("@heroui/theme").SlotsToClasses<"base" | "label" | "startContent" | "endContent" | "wrapper" | "hiddenInput" | "thumb" | "thumbIcon"> | undefined;
293
293
  isDisabled?: boolean | undefined;
294
294
  isReadOnly?: boolean | undefined;
295
295
  onFocusChange?: ((isFocused: boolean) => void) | undefined;
@@ -286,13 +286,13 @@ export declare const UXTable: import("@heroui/system-rsc").InternalForwardRefRen
286
286
  onTransitionStartCapture?: React.TransitionEventHandler<HTMLTableElement> | undefined;
287
287
  align?: "start" | "end" | "center" | undefined;
288
288
  size?: "md" | "lg" | undefined;
289
+ summary?: string | undefined;
289
290
  width?: string | number | undefined;
290
291
  radius?: "sm" | "md" | "lg" | "none" | undefined;
291
292
  layout?: "auto" | "fixed" | undefined;
292
293
  border?: number | undefined;
293
294
  classNames?: import("@heroui/theme").SlotsToClasses<"table" | "base" | "tbody" | "td" | "tfoot" | "th" | "thead" | "tr" | "wrapper" | "sortIcon" | "emptyWrapper" | "loadingWrapper"> | undefined;
294
295
  disableAnimation?: boolean | undefined;
295
- summary?: string | undefined;
296
296
  as?: import("@heroui/system-rsc").As<any> | undefined;
297
297
  key?: React.Key | null | undefined;
298
298
  baseRef?: import("@heroui/react-utils").ReactRef<HTMLElement | null> | undefined;
@@ -283,13 +283,13 @@ declare const ExtendedTable: import("react").ForwardRefExoticComponent<Omit<{
283
283
  onTransitionStartCapture?: import("react").TransitionEventHandler<HTMLTableElement> | undefined;
284
284
  align?: "start" | "end" | "center" | undefined;
285
285
  size?: "md" | "lg" | undefined;
286
+ summary?: string | undefined;
286
287
  width?: string | number | undefined;
287
288
  radius?: "sm" | "md" | "lg" | "none" | undefined;
288
289
  layout?: "auto" | "fixed" | undefined;
289
290
  border?: number | undefined;
290
291
  classNames?: import("@heroui/theme").SlotsToClasses<"table" | "base" | "tbody" | "td" | "tfoot" | "th" | "thead" | "tr" | "wrapper" | "sortIcon" | "emptyWrapper" | "loadingWrapper"> | undefined;
291
292
  disableAnimation?: boolean | undefined;
292
- summary?: string | undefined;
293
293
  as?: import("@heroui/system-rsc").As<any> | undefined;
294
294
  key?: import("react").Key | null | undefined;
295
295
  baseRef?: import("@heroui/react-utils").ReactRef<HTMLElement | null> | undefined;
@@ -7,17 +7,15 @@ import { Text } from "../typography/Text.js";
7
7
  const ThemeItem = ({ id, zhName, enName, isSelected, onClick })=>{
8
8
  const lang = useLang();
9
9
  return /*#__PURE__*/ jsxs(VStack, {
10
- center: true,
11
10
  gap: 2,
11
+ items: "center",
12
12
  className: cn('cursor-pointer', id),
13
13
  onClick: onClick,
14
14
  children: [
15
15
  /*#__PURE__*/ jsx("div", {
16
- className: cn('rounded-medium border-2 hover:scale-105 transition-all duration-300', isSelected ? 'border-primary' : 'border-transparent'),
16
+ className: cn('rounded-medium border-2 hover:scale-105 transition-all duration-300', 'w-[100px] h-[59px] md:w-[120px] md:h-[71px]', isSelected ? 'border-primary' : 'border-transparent'),
17
17
  children: /*#__PURE__*/ jsxs("svg", {
18
18
  xmlns: "http://www.w3.org/2000/svg",
19
- width: "120",
20
- height: "71",
21
19
  viewBox: "0 0 120 71",
22
20
  fill: "none",
23
21
  children: [
@@ -138,6 +136,7 @@ const ThemeItem = ({ id, zhName, enName, isSelected, onClick })=>{
138
136
  })
139
137
  }),
140
138
  /*#__PURE__*/ jsx(Text, {
139
+ align: "center",
141
140
  children: 'zh' === lang ? zhName : enName
142
141
  })
143
142
  ]
@@ -1,7 +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
+ import { type UXModalProps } from '../UXModal';
3
+ export type UXThemeSwitchModalProps = Omit<UXModalProps, 'children'>;
4
+ export declare const UXThemeSwitchModal: React.FC<UXThemeSwitchModalProps>;
5
5
  export interface UXThemeSwitchProps {
6
6
  children?: (onOpen: () => void) => React.ReactNode;
7
7
  }
@@ -1,113 +1,158 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
- import "react";
2
+ import { useState } from "react";
3
+ import { cn } from "@heroui/theme";
3
4
  import { useDisclosure } from "@heroui/use-disclosure";
4
- import { ChartColorSwitchIcon } from "@particle-network/icons/web";
5
+ import { ChartColorSwitchIcon, CopyIcon } from "@particle-network/icons/web";
5
6
  import { themeData } from "@particle-network/ui-shared";
6
7
  import { useI18n } from "../../hooks/index.js";
8
+ import { UXCopy, UXTooltip } from "../index.js";
7
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
- import { UXDrawer } from "../UXDrawer/index.js";
12
13
  import { UXInput } from "../UXInput/index.js";
14
+ import { UXModal } from "../UXModal/index.js";
13
15
  import { UXSpinner } from "../UXSpinner/index.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 } = 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__*/ jsxs(UXDrawer, {
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
38
  title: i18n.theme.title,
39
- footer: /*#__PURE__*/ jsxs(HStack, {
39
+ footer: /*#__PURE__*/ jsx(UXButton, {
40
40
  fullWidth: true,
41
+ size: "lg",
42
+ color: "primary",
43
+ onPress: onClose,
44
+ children: i18n.theme.done
45
+ }),
46
+ onOpenChange: onOpenChange,
47
+ ...props,
48
+ children: /*#__PURE__*/ jsxs(VStack, {
41
49
  gap: "lg",
42
50
  children: [
43
- /*#__PURE__*/ jsx(UXButton, {
44
- fullWidth: true,
45
- size: "lg",
46
- onPress: handleReset,
47
- children: i18n.theme.reset
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))
58
+ }),
59
+ /*#__PURE__*/ jsx(UXDivider, {}),
60
+ /*#__PURE__*/ jsxs(VStack, {
61
+ gap: "md",
62
+ children: [
63
+ /*#__PURE__*/ jsx(Text, {
64
+ body1Bold: true,
65
+ children: i18n.theme.font.title
66
+ }),
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",
81
+ children: [
82
+ i18n.theme.font.success,
83
+ fontName
84
+ ]
85
+ }),
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
95
+ })
96
+ ]
97
+ }),
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
+ ]
48
110
  }),
49
111
  /*#__PURE__*/ jsx(UXButton, {
50
- fullWidth: true,
51
- isDisabled: 'error' === fontLoadStatus,
52
- size: "lg",
112
+ className: "self-start",
113
+ variant: "text",
53
114
  color: "primary",
54
- onPress: handleApplyTheme,
55
- children: i18n.theme.apply
56
- })
57
- ]
58
- }),
59
- onOpenChange: handleOpenChange,
60
- ...props,
61
- children: [
62
- /*#__PURE__*/ jsx("div", {
63
- className: "grid grid-cols-3 gap-y-5",
64
- children: themeData.map((theme)=>/*#__PURE__*/ jsx(ThemeItem, {
65
- isSelected: selectedTheme.id === theme.id,
66
- onClick: ()=>setSelectedTheme(theme),
67
- ...theme
68
- }, theme.id))
69
- }),
70
- /*#__PURE__*/ jsx(UXDivider, {
71
- className: "my-lg"
72
- }),
73
- /*#__PURE__*/ jsxs(VStack, {
74
- gap: "md",
75
- children: [
76
- /*#__PURE__*/ jsx(Text, {
77
- body1Bold: true,
78
- children: i18n.theme.font.title
79
- }),
80
- /*#__PURE__*/ jsx(UXInput, {
81
- isClearable: true,
82
- startContent: 'loading' === fontLoadStatus && /*#__PURE__*/ jsx(UXSpinner, {
83
- size: 18
84
- }),
85
- isInvalid: 'error' === fontLoadStatus,
86
- className: "w-full",
87
- placeholder: i18n.theme.font.placeholder,
88
- value: selectedFontLink,
89
- description: /*#__PURE__*/ jsxs(Fragment, {
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",
90
125
  children: [
91
- 'success' === fontLoadStatus && /*#__PURE__*/ jsxs(Text, {
92
- body3Bold: true,
93
- color: "success",
94
- children: [
95
- i18n.theme.font.success,
96
- fontName
97
- ]
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
+ })
98
138
  }),
99
- 'error' === fontLoadStatus && /*#__PURE__*/ jsx(Text, {
100
- body3Bold: true,
101
- color: "danger",
102
- children: i18n.theme.font.error
139
+ /*#__PURE__*/ jsx(UXCopy, {
140
+ copyText: example.url,
141
+ toastText: example.url,
142
+ children: /*#__PURE__*/ jsx(UXButton, {
143
+ isIconOnly: true,
144
+ size: "sm",
145
+ variant: "light",
146
+ children: /*#__PURE__*/ jsx(CopyIcon, {
147
+ color: "secondary"
148
+ })
149
+ })
103
150
  })
104
151
  ]
105
- }),
106
- onValueChange: setSelectedFontLink
107
- })
108
- ]
109
- })
110
- ]
152
+ }, example.title))
153
+ })
154
+ ]
155
+ })
111
156
  });
112
157
  };
113
158
  const UXThemeSwitch = ({ children })=>{
@@ -124,7 +169,7 @@ const UXThemeSwitch = ({ children })=>{
124
169
  return /*#__PURE__*/ jsxs(Fragment, {
125
170
  children: [
126
171
  renderChildren(),
127
- /*#__PURE__*/ jsx(UXThemeSwitchDrawer, {
172
+ /*#__PURE__*/ jsx(UXThemeSwitchModal, {
128
173
  isOpen: isOpen,
129
174
  onClose: onClose,
130
175
  onOpenChange: onOpenChange
@@ -132,4 +177,4 @@ const UXThemeSwitch = ({ children })=>{
132
177
  ]
133
178
  });
134
179
  };
135
- export { UXThemeSwitch, UXThemeSwitchDrawer };
180
+ export { UXThemeSwitch, UXThemeSwitchModal };
@@ -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,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
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
  */
@@ -27,10 +19,8 @@ interface State {
27
19
  fontLoadStatus: FontLoadStatus;
28
20
  }
29
21
  interface Actions {
30
- setSelectedTheme: (theme: ThemeItemType) => void;
31
- setSavedTheme: (theme: ThemeItemType) => void;
32
- setSelectedFontLink: (link: string) => void;
33
- setSavedFontLink: (link: string) => void;
22
+ setTheme: (theme: ThemeItemType) => void;
23
+ setFontUrl: (fontUrl: string) => void;
34
24
  setFontLoadStatus: (status: FontLoadStatus) => void;
35
25
  setFontName: (name: string) => void;
36
26
  }
@@ -2,39 +2,29 @@ import { themeData } from "@particle-network/ui-shared";
2
2
  import { create } from "zustand";
3
3
  import { createJSONStorage, persist } from "zustand/middleware";
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
- setSelectedTheme: (theme)=>set({
12
- selectedTheme: theme
9
+ setTheme: (theme)=>set({
10
+ theme
13
11
  }),
14
- setSavedTheme: (theme)=>set({
15
- savedTheme: theme
12
+ setFontUrl: (fontUrl)=>set({
13
+ fontUrl
16
14
  }),
17
- setSelectedFontLink: (link)=>set({
18
- selectedFontLink: link
15
+ setFontLoadStatus: (fontLoadStatus)=>set({
16
+ fontLoadStatus
19
17
  }),
20
- setSavedFontLink: (link)=>set({
21
- savedFontLink: link
22
- }),
23
- setFontLoadStatus: (status)=>set({
24
- fontLoadStatus: status
25
- }),
26
- setFontName: (name)=>set({
27
- fontName: name
18
+ setFontName: (fontName)=>set({
19
+ fontName
28
20
  })
29
21
  }), {
30
- name: 'ux-theme-1',
22
+ name: 'ux-theme-2',
31
23
  storage: createJSONStorage(()=>'undefined' != typeof window ? window.localStorage : {}),
32
24
  partialize: (state)=>({
33
- selectedTheme: state.selectedTheme,
34
- savedTheme: state.savedTheme,
25
+ theme: state.theme,
35
26
  fontName: state.fontName,
36
- selectedFontLink: state.selectedFontLink,
37
- savedFontLink: state.savedFontLink
27
+ fontUrl: state.fontUrl
38
28
  })
39
29
  }));
40
30
  export { useThemeStore };
@@ -3,14 +3,11 @@ import { type ThemeItemType } from '@particle-network/ui-shared';
3
3
  * UX 主题管理 Hook
4
4
  */
5
5
  export declare const useTheme: () => {
6
- selectedTheme: ThemeItemType;
7
- setSelectedTheme: (theme: ThemeItemType) => void;
8
- savedTheme: ThemeItemType;
9
- setSavedTheme: (theme: ThemeItemType) => void;
10
- selectedFontLink: string;
11
- setSelectedFontLink: (link: string) => void;
12
- savedFontLink: string;
13
- setSavedFontLink: (link: string) => void;
6
+ theme: ThemeItemType;
7
+ setTheme: (theme: ThemeItemType) => void;
8
+ fontUrl: string;
9
+ setFontUrl: (link: string) => void;
10
+ clearFontUrl: () => void;
14
11
  fontLoadStatus: import("./use-theme-store").FontLoadStatus;
15
12
  fontName: string;
16
13
  };
@@ -49,32 +49,28 @@ const applyFont = (fontName)=>{
49
49
  }
50
50
  };
51
51
  const useTheme = ()=>{
52
- const selectedTheme = useThemeStore((state)=>state.selectedTheme);
53
- const savedTheme = useThemeStore((state)=>state.savedTheme);
54
- const selectedFontLink = useThemeStore((state)=>state.selectedFontLink);
55
- const savedFontLink = useThemeStore((state)=>state.savedFontLink);
52
+ const theme = useThemeStore((state)=>state.theme);
53
+ const fontUrl = useThemeStore((state)=>state.fontUrl);
56
54
  const fontName = useThemeStore((state)=>state.fontName);
57
55
  const fontLoadStatus = useThemeStore((state)=>state.fontLoadStatus);
58
- const storeSetSelectedTheme = useThemeStore((state)=>state.setSelectedTheme);
59
- const storeSetSavedTheme = useThemeStore((state)=>state.setSavedTheme);
60
- const storeSetSelectedFontLink = useThemeStore((state)=>state.setSelectedFontLink);
61
- const storeSetSavedFontLink = useThemeStore((state)=>state.setSavedFontLink);
56
+ const storeSetTheme = useThemeStore((state)=>state.setTheme);
57
+ const storeSetFontUrl = useThemeStore((state)=>state.setFontUrl);
62
58
  const storeSetFontLoadStatus = useThemeStore((state)=>state.setFontLoadStatus);
63
59
  const storeSetFontName = useThemeStore((state)=>state.setFontName);
64
- const applyFontWithStatus = (customFontLink, themeFontName)=>{
60
+ const applyFontWithStatus = (customFontUrl, themeFontName)=>{
65
61
  if ('undefined' == typeof window) return;
66
- const isCustomFont = !!customFontLink?.trim();
67
- if (!isCustomFont) return void applyFont(themeFontName);
62
+ const url = customFontUrl?.trim();
63
+ if (!url) return void applyFont(themeFontName);
68
64
  const existingLink = document.getElementById('ux-google-font-link');
69
65
  if (existingLink) existingLink.remove();
70
66
  storeSetFontLoadStatus('loading');
71
67
  const linkElement = document.createElement('link');
72
68
  linkElement.id = 'ux-google-font-link';
73
69
  linkElement.rel = 'stylesheet';
74
- linkElement.href = customFontLink;
70
+ linkElement.href = url;
75
71
  linkElement.onload = ()=>{
76
72
  storeSetFontLoadStatus('success');
77
- const fontFamily = extractFontFamilyFromLink(customFontLink);
73
+ const fontFamily = extractFontFamilyFromLink(url);
78
74
  if (fontFamily) {
79
75
  applyFont(fontFamily);
80
76
  storeSetFontName(fontFamily);
@@ -92,48 +88,30 @@ const useTheme = ()=>{
92
88
  });
93
89
  useEffect(()=>{
94
90
  preloadFonts();
95
- }, []);
96
- useEffect(()=>{
97
- if (!selectedFontLink.trim()) {
98
- storeSetFontLoadStatus('idle');
99
- storeSetFontName('');
100
- }
101
- }, [
102
- selectedFontLink
103
- ]);
104
- useEffect(()=>{
105
- applyTheme(savedTheme);
106
- debouncedApplyFont(savedFontLink, savedTheme.fontName);
107
- }, [
108
- savedTheme,
109
- savedFontLink
110
- ]);
111
- const setSelectedTheme = (theme)=>{
112
- storeSetSelectedTheme(theme);
113
91
  applyTheme(theme);
114
- debouncedApplyFont(selectedFontLink, theme.fontName);
115
- };
116
- const setSavedTheme = (theme)=>{
117
- storeSetSavedTheme(theme);
92
+ debouncedApplyFont(fontUrl, theme.fontName);
93
+ }, []);
94
+ const setTheme = (theme)=>{
95
+ storeSetTheme(theme);
118
96
  applyTheme(theme);
97
+ debouncedApplyFont(fontUrl, theme.fontName);
119
98
  };
120
- const setSelectedFontLink = (link)=>{
121
- storeSetSelectedFontLink(link);
122
- debouncedApplyFont(link, selectedTheme.fontName);
99
+ const setFontUrl = (link)=>{
100
+ storeSetFontUrl(link);
101
+ debouncedApplyFont(link, theme.fontName);
123
102
  };
124
- const setSavedFontLink = (link)=>{
125
- storeSetSavedFontLink(link);
126
- debouncedApplyFont(link, savedTheme.fontName);
103
+ const clearFontUrl = ()=>{
104
+ storeSetFontUrl('');
105
+ storeSetFontName('');
106
+ storeSetFontLoadStatus('idle');
107
+ debouncedApplyFont('', theme.fontName);
127
108
  };
128
109
  return {
129
- selectedTheme,
130
- setSelectedTheme,
131
- savedTheme,
132
- setSavedTheme,
133
- selectedFontLink,
134
- setSelectedFontLink,
135
- savedFontLink,
136
- setSavedFontLink,
110
+ theme,
111
+ setTheme,
112
+ fontUrl,
113
+ setFontUrl,
114
+ clearFontUrl,
137
115
  fontLoadStatus,
138
116
  fontName
139
117
  };
@@ -10,14 +10,18 @@ export declare const useI18n: () => {
10
10
  };
11
11
  theme: {
12
12
  title: string;
13
- reset: string;
14
- apply: string;
13
+ done: string;
15
14
  font: {
16
15
  title: string;
17
16
  placeholder: string;
18
17
  loading: string;
19
18
  success: string;
20
19
  error: string;
20
+ hint: string;
21
+ example: {
22
+ show: string;
23
+ hide: string;
24
+ };
21
25
  };
22
26
  };
23
27
  };
@@ -11,14 +11,18 @@ const en = {
11
11
  },
12
12
  theme: {
13
13
  title: 'Theme',
14
- reset: 'Reset',
15
- apply: 'Apply Theme',
14
+ done: 'Finish',
16
15
  font: {
17
16
  title: 'Custom Font',
18
17
  placeholder: 'Enter Google Fonts URL or custom font URL',
19
18
  loading: 'Loading font...',
20
19
  success: 'Font loaded: ',
21
- error: 'Failed to load font'
20
+ error: 'Failed to load font',
21
+ hint: 'Try Google Fonts: ',
22
+ example: {
23
+ show: 'Show example',
24
+ hide: 'Hide example'
25
+ }
22
26
  }
23
27
  }
24
28
  };
@@ -34,14 +38,18 @@ const zh = {
34
38
  },
35
39
  theme: {
36
40
  title: '主题',
37
- reset: '重置',
38
- apply: '应用主题',
41
+ done: '完成',
39
42
  font: {
40
43
  title: '自定义字体',
41
44
  placeholder: '输入 Google Fonts 或自定义字体链接',
42
45
  loading: '正在加载字体...',
43
- success: '字体加载成功:',
44
- error: '字体加载失败'
46
+ success: '已加载字体:',
47
+ error: '字体加载失败',
48
+ hint: '参考 Google Fonts: ',
49
+ example: {
50
+ show: '显示示例',
51
+ hide: '隐藏示例'
52
+ }
45
53
  }
46
54
  }
47
55
  };
@@ -1,7 +1,6 @@
1
1
  const hasLongWord = (text)=>{
2
2
  if ('string' != typeof text) return false;
3
- const englishWords = text.match(/[a-zA-Z0-9_-]+/g);
4
- if (!englishWords) return false;
3
+ const englishWords = text.split(' ');
5
4
  return englishWords.some((word)=>word.length > 30);
6
5
  };
7
6
  export { hasLongWord };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@particle-network/ui-react",
3
- "version": "0.4.0-beta.24",
3
+ "version": "0.4.0-beta.25",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -49,8 +49,8 @@
49
49
  "ahooks": "^3.9.4",
50
50
  "copy-to-clipboard": "^3.3.3",
51
51
  "zustand": "^5.0.8",
52
- "@particle-network/ui-shared": "0.3.0-beta.6",
53
- "@particle-network/icons": "0.4.0-beta.14"
52
+ "@particle-network/icons": "0.4.0-beta.14",
53
+ "@particle-network/ui-shared": "0.3.0-beta.6"
54
54
  },
55
55
  "scripts": {
56
56
  "build": "rslib build",