@particle-network/ui-react 0.5.1-beta.2 → 0.5.1-beta.4

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.
@@ -8,10 +8,12 @@ import RefreshCcwIcon from "@particle-network/icons/web/RefreshCcwIcon";
8
8
  import { HStack } from "../layout/index.js";
9
9
  import { UXButton } from "../UXButton/index.js";
10
10
  import { UXPopover, UXPopoverContent, UXPopoverTrigger } from "../UXPopover/index.js";
11
+ import { useThemeColor } from "../UXThemeSwitch/index.js";
11
12
  import { ColorFields } from "./color-fields.js";
12
13
  import { ColorInput } from "./color-input.js";
13
14
  import { normalizeColor } from "./utils.js";
14
- const UXColorPicker = ({ className, value, defaultValue, onChange, onChangeEnd, onValueChange, onValueChangeEnd, isDisabled, placement = 'bottom-start' })=>{
15
+ const UXColorPicker = ({ className, isDisabled, placement = 'bottom-start', value, defaultValue, onChange, onChangeEnd, onValueChange, onValueChangeEnd, isChanged, onReset })=>{
16
+ const { 'bg-200': bg200 } = useThemeColor();
15
17
  const isControlled = void 0 !== value;
16
18
  const [pickerKey, setPickerKey] = useState(0);
17
19
  const [isInputFocused, setIsInputFocused] = useState(false);
@@ -92,13 +94,16 @@ const UXColorPicker = ({ className, value, defaultValue, onChange, onChangeEnd,
92
94
  onValueChangeEnd
93
95
  ]);
94
96
  const hasChanged = useMemo(()=>{
97
+ if (void 0 !== isChanged) return isChanged;
95
98
  const initialHex = initialColorRef.current.toString('hex');
96
99
  const currentHex = currentColor.toString('hex');
97
100
  return initialHex !== currentHex;
98
101
  }, [
99
- currentColor
102
+ currentColor,
103
+ isChanged
100
104
  ]);
101
105
  const handleReset = ()=>{
106
+ if (onReset) return void onReset();
102
107
  const resetColor = initialColorRef.current;
103
108
  if (!isControlled) {
104
109
  setInternalColor(resetColor);
@@ -130,10 +135,11 @@ const UXColorPicker = ({ className, value, defaultValue, onChange, onChangeEnd,
130
135
  children: [
131
136
  /*#__PURE__*/ jsx(UXPopoverTrigger, {
132
137
  children: /*#__PURE__*/ jsx(UXButton, {
138
+ radius: "none",
133
139
  size: "auto",
134
140
  isDisabled: isDisabled,
135
141
  children: /*#__PURE__*/ jsx(ColorSwatch, {
136
- className: "h-4 w-4 rounded-sm border-none"
142
+ className: cn('h-4 w-4 rounded-[4px]', internalColor.toString('hex') === bg200 && 'border-foreground/10 border')
137
143
  })
138
144
  })
139
145
  }),
@@ -149,7 +155,7 @@ const UXColorPicker = ({ className, value, defaultValue, onChange, onChangeEnd,
149
155
  className: "aspect-square w-full overflow-hidden rounded-lg",
150
156
  onChangeEnd: handleChangeEnd,
151
157
  children: /*#__PURE__*/ jsx(ColorThumb, {
152
- className: "border-1 h-2 w-2 rounded-full border-white shadow-[0_0_0_0.5px_black,inset_0_0_0_0.5px_black]"
158
+ className: "h-4 w-4 rounded-full border-2 border-white shadow-[0_2px_4px_rgba(0,0,0,0.2)]"
153
159
  })
154
160
  }),
155
161
  /*#__PURE__*/ jsxs(HStack, {
@@ -177,7 +183,7 @@ const UXColorPicker = ({ className, value, defaultValue, onChange, onChangeEnd,
177
183
  children: /*#__PURE__*/ jsx(SliderTrack, {
178
184
  className: "h-3 rounded-full",
179
185
  children: /*#__PURE__*/ jsx(ColorThumb, {
180
- className: "top-[50%] z-10 h-4 w-4 rounded-full border-2 border-white"
186
+ className: "top-[50%] z-10 h-4 w-4 rounded-full border-2 border-white shadow-[0_2px_4px_rgba(0,0,0,0.2)]"
181
187
  })
182
188
  })
183
189
  })
@@ -195,10 +201,10 @@ const UXColorPicker = ({ className, value, defaultValue, onChange, onChangeEnd,
195
201
  }),
196
202
  /*#__PURE__*/ jsx(ColorInput, {
197
203
  isDisabled: isDisabled,
198
- inputClassName: "focus:ring-0 px-0 text-left flex-1",
204
+ inputClassName: "focus:ring-0 px-0 text-left flex-1 relative h-7",
199
205
  value: value,
200
206
  defaultValue: !isControlled && pickerKey > 0 ? internalColor : defaultValue,
201
- onChange: (color)=>handleChange(color),
207
+ onChange: handleColorFieldsChange,
202
208
  onFocus: ()=>setIsInputFocused(true),
203
209
  onBlur: ()=>setIsInputFocused(false)
204
210
  }),
@@ -26,10 +26,21 @@ export interface UXColorPickerProps {
26
26
  * HEX 格式颜色变化结束回调(释放时调用)
27
27
  */
28
28
  onValueChangeEnd?: (color: string) => void;
29
+ /**
30
+ * 是否已改变
31
+ */
32
+ isChanged?: boolean;
33
+ /**
34
+ * 重置回调
35
+ */
36
+ onReset?: () => void;
29
37
  /**
30
38
  * 是否禁用
31
39
  */
32
40
  isDisabled?: boolean;
41
+ /**
42
+ * Popover 位置
43
+ */
33
44
  placement?: UXPopoverProps['placement'];
34
45
  }
35
46
  export interface ColorFieldsProps extends Pick<UXColorPickerProps, 'value' | 'defaultValue'> {
@@ -0,0 +1,2 @@
1
+ import React from 'react';
2
+ export declare const CustomThemeConfig: React.FC;
@@ -0,0 +1,132 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { useMemo } from "react";
3
+ import { DEFAULT_THEME_ID, themeData } from "@particle-network/ui-shared";
4
+ import { produce } from "immer";
5
+ import { useI18n, useLang } from "../../hooks/index.js";
6
+ import { HStack, VStack } from "../layout/index.js";
7
+ import { Text } from "../typography/Text.js";
8
+ import { UXColorPicker } from "../UXColorPicker/index.js";
9
+ import { UXSelect, UXSelectItem } from "../UXSelect/index.js";
10
+ import { UXTab, UXTabs } from "../UXTabs/index.js";
11
+ import { useTheme } from "./use-theme.js";
12
+ const COLOR_CATEGORIES = {
13
+ basic: [
14
+ 'primary',
15
+ 'bullish',
16
+ 'bearish'
17
+ ],
18
+ background: [
19
+ 'bg-default',
20
+ 'bg-400',
21
+ 'bg-300',
22
+ 'bg-200',
23
+ 'overlay',
24
+ 'divider'
25
+ ],
26
+ text: [
27
+ 'foreground',
28
+ 'secondary',
29
+ 'tertiary'
30
+ ],
31
+ signal: [
32
+ 'success',
33
+ 'danger',
34
+ 'alert',
35
+ 'gold'
36
+ ]
37
+ };
38
+ const CustomThemeConfig = ()=>{
39
+ const { lang } = useLang();
40
+ const i18n = useI18n();
41
+ const { theme, setTheme } = useTheme();
42
+ const baseThemes = useMemo(()=>themeData.filter((t)=>'custom' !== t.id), []);
43
+ const baseThemeId = theme.baseThemeId || DEFAULT_THEME_ID;
44
+ const baseTheme = useMemo(()=>themeData.find((t)=>t.id === baseThemeId) || themeData.find((t)=>t.id === DEFAULT_THEME_ID), [
45
+ baseThemeId
46
+ ]);
47
+ const handleBaseThemeChange = (themeId)=>{
48
+ const selectedBaseTheme = themeData.find((t)=>t.id === themeId);
49
+ if (selectedBaseTheme) setTheme({
50
+ ...theme,
51
+ baseThemeId: themeId,
52
+ colorScheme: selectedBaseTheme.colorScheme,
53
+ colorVariables: selectedBaseTheme.colorVariables
54
+ });
55
+ };
56
+ const handleColorChange = (color, value)=>{
57
+ const updatedTheme = produce(theme, (draft)=>{
58
+ draft.colorVariables[color] = value;
59
+ });
60
+ setTheme(updatedTheme);
61
+ };
62
+ const getColorValue = (color)=>theme.colorVariables[color] || baseTheme.colorVariables[color] || '#000000';
63
+ const renderColorItem = (color, label)=>{
64
+ const value = getColorValue(color);
65
+ const isCustomized = void 0 !== theme.colorVariables[color] && theme.colorVariables[color] !== baseTheme.colorVariables[color];
66
+ return /*#__PURE__*/ jsxs(HStack, {
67
+ justify: "between",
68
+ children: [
69
+ /*#__PURE__*/ jsx(Text, {
70
+ color: "secondary",
71
+ children: label
72
+ }),
73
+ /*#__PURE__*/ jsx(UXColorPicker, {
74
+ value: value,
75
+ isChanged: isCustomized,
76
+ onValueChange: (newValue)=>handleColorChange(color, newValue),
77
+ onReset: ()=>handleColorChange(color, baseTheme.colorVariables[color])
78
+ })
79
+ ]
80
+ }, color);
81
+ };
82
+ return /*#__PURE__*/ jsxs(VStack, {
83
+ gap: "lg",
84
+ children: [
85
+ /*#__PURE__*/ jsx(HStack, {
86
+ justify: "between",
87
+ children: /*#__PURE__*/ jsx(Text, {
88
+ body1Bold: true,
89
+ children: i18n.theme.custom.title
90
+ })
91
+ }),
92
+ /*#__PURE__*/ jsxs(HStack, {
93
+ justify: "between",
94
+ children: [
95
+ /*#__PURE__*/ jsx(Text, {
96
+ color: "secondary",
97
+ children: i18n.theme.custom.preset
98
+ }),
99
+ /*#__PURE__*/ jsx(UXSelect, {
100
+ className: "min-w-48",
101
+ selectedKeys: [
102
+ baseThemeId
103
+ ],
104
+ onSelectionChange: (keys)=>{
105
+ const selectedKey = Array.from(keys)[0];
106
+ if (selectedKey) handleBaseThemeChange(selectedKey);
107
+ },
108
+ children: baseThemes.map((t)=>/*#__PURE__*/ jsx(UXSelectItem, {
109
+ children: 'zh' === lang ? t.zhName : t.enName
110
+ }, t.id))
111
+ })
112
+ ]
113
+ }),
114
+ /*#__PURE__*/ jsx(UXTabs, {
115
+ fullWidth: true,
116
+ variant: "switch",
117
+ children: Object.entries(COLOR_CATEGORIES).map(([category, colors])=>/*#__PURE__*/ jsx(UXTab, {
118
+ title: i18n.theme.custom.categories[category],
119
+ children: /*#__PURE__*/ jsx(VStack, {
120
+ gap: "md",
121
+ className: "pt-md",
122
+ children: colors.map((color)=>{
123
+ const label = i18n.theme.custom.colors[color] || color;
124
+ return renderColorItem(color, label);
125
+ })
126
+ })
127
+ }, category))
128
+ })
129
+ ]
130
+ });
131
+ };
132
+ export { CustomThemeConfig };
@@ -1,11 +1,11 @@
1
- import { jsx, jsxs } from "react/jsx-runtime";
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import "react";
3
3
  import { cn } from "@heroui/theme";
4
4
  import { useLang } from "../../hooks/index.js";
5
5
  import { VStack } from "../layout/index.js";
6
6
  import { Text } from "../typography/Text.js";
7
7
  const ThemeItem = ({ id, zhName, enName, isSelected, onClick })=>{
8
- const lang = useLang();
8
+ const { lang } = useLang();
9
9
  return /*#__PURE__*/ jsxs(VStack, {
10
10
  gap: 2,
11
11
  items: "center",
@@ -123,7 +123,50 @@ const ThemeItem = ({ id, zhName, enName, isSelected, onClick })=>{
123
123
  fill: "currentColor",
124
124
  className: "text-secondary"
125
125
  }),
126
- /*#__PURE__*/ jsx("rect", {
126
+ 'custom' === id ? /*#__PURE__*/ jsxs(Fragment, {
127
+ children: [
128
+ /*#__PURE__*/ jsx("g", {
129
+ "clip-path": "url(#paint0_angular_48986_280686_clip_path)",
130
+ "data-figma-skip-parse": "true",
131
+ children: /*#__PURE__*/ jsx("g", {
132
+ transform: "matrix(0 0.011 -0.011 0 39 51)",
133
+ children: /*#__PURE__*/ jsx("foreignObject", {
134
+ x: "-1043.94",
135
+ y: "-1043.94",
136
+ width: "2087.87",
137
+ height: "2087.87",
138
+ children: /*#__PURE__*/ jsx("div", {
139
+ style: {
140
+ background: 'conic-gradient(from 90deg,rgba(255, 0, 0, 1) 0deg,rgba(255, 133, 0, 1) 54deg,rgba(190, 255, 0, 1) 117deg,rgba(7, 225, 255, 1) 199.8deg,rgba(35, 0, 255, 1) 282.6deg,rgba(173, 0, 255, 1) 360deg)',
141
+ height: '100%',
142
+ width: '100%',
143
+ opacity: 1
144
+ }
145
+ })
146
+ })
147
+ })
148
+ }),
149
+ /*#__PURE__*/ jsx("circle", {
150
+ cx: "39",
151
+ cy: "51",
152
+ r: "10.7583",
153
+ "data-figma-gradient-fill": '{"type":"GRADIENT_ANGULAR","stops":[{"color":{"r":1.0,"g":0.0,"b":0.0,"a":1.0},"position":0.0},{"color":{"r":1.0,"g":0.52295231819152832,"b":0.0,"a":1.0},"position":0.15000000596046448},{"color":{"r":0.74555355310440063,"g":1.0,"b":0.0,"a":1.0},"position":0.32499998807907104},{"color":{"r":0.029410807415843010,"g":0.88352936506271362,"b":1.0,"a":1.0},"position":0.55500000715255737},{"color":{"r":0.14000000059604645,"g":0.0,"b":1.0,"a":1.0},"position":0.78500002622604370},{"color":{"r":0.67999994754791260,"g":0.0,"b":1.0,"a":1.0},"position":1.0}],"stopsVar":[{"color":{"r":1.0,"g":0.0,"b":0.0,"a":1.0},"position":0.0},{"color":{"r":1.0,"g":0.52295231819152832,"b":0.0,"a":1.0},"position":0.15000000596046448},{"color":{"r":0.74555355310440063,"g":1.0,"b":0.0,"a":1.0},"position":0.32499998807907104},{"color":{"r":0.029410807415843010,"g":0.88352936506271362,"b":1.0,"a":1.0},"position":0.55500000715255737},{"color":{"r":0.14000000059604645,"g":0.0,"b":1.0,"a":1.0},"position":0.78500002622604370},{"color":{"r":0.67999994754791260,"g":0.0,"b":1.0,"a":1.0},"position":1.0}],"transform":{"m00":1.3471115643134642e-15,"m01":-22.0,"m02":50.0,"m10":22.0,"m11":1.3471115643134642e-15,"m12":40.0},"opacity":1.0,"blendMode":"NORMAL","visible":true}',
154
+ stroke: "#8B8EA1",
155
+ "stroke-width": "0.483303"
156
+ }),
157
+ /*#__PURE__*/ jsx("defs", {
158
+ children: /*#__PURE__*/ jsx("clipPath", {
159
+ id: "paint0_angular_48986_280686_clip_path",
160
+ children: /*#__PURE__*/ jsx("circle", {
161
+ cx: "39",
162
+ cy: "51",
163
+ r: "10.7583",
164
+ "stroke-width": "0.483303"
165
+ })
166
+ })
167
+ })
168
+ ]
169
+ }) : /*#__PURE__*/ jsx("rect", {
127
170
  x: "27",
128
171
  y: "39",
129
172
  width: "22.5688",
@@ -3,7 +3,7 @@ import { useMemo, useState } from "react";
3
3
  import { cn } from "@heroui/theme";
4
4
  import { useDisclosure } from "@heroui/use-disclosure";
5
5
  import { ChartColorSwitchIcon, CopyIcon } from "@particle-network/icons/web";
6
- import { themeData } from "@particle-network/ui-shared";
6
+ import { DEFAULT_THEME_ID, themeData } from "@particle-network/ui-shared";
7
7
  import { useI18n } from "../../hooks/index.js";
8
8
  import { HStack, VStack } from "../layout/index.js";
9
9
  import { Text } from "../typography/Text.js";
@@ -15,6 +15,7 @@ import { UXInput } from "../UXInput/index.js";
15
15
  import { UXModal } from "../UXModal/index.js";
16
16
  import { UXSpinner } from "../UXSpinner/index.js";
17
17
  import { UXTooltip } from "../UXTooltip/index.js";
18
+ import { CustomThemeConfig } from "./custom-theme-config.js";
18
19
  import { ThemeItem } from "./theme-item.js";
19
20
  import { useTheme } from "./use-theme.js";
20
21
  const FONT_EXAMPLES = [
@@ -39,6 +40,18 @@ const UXThemeSwitchModal = ({ as = 'modal', omitThemes = [], backdrop, isOpen, o
39
40
  const themes = useMemo(()=>themeData.filter((theme)=>!omitThemes.includes(theme.id)), [
40
41
  omitThemes
41
42
  ]);
43
+ const handleThemeSelect = (theme)=>{
44
+ if ('custom' === theme.id && 'custom' !== selectedTheme.id) {
45
+ const baseTheme = themeData.find((t)=>t.id === DEFAULT_THEME_ID) || themeData["0"];
46
+ const customTheme = {
47
+ ...theme,
48
+ baseThemeId: DEFAULT_THEME_ID,
49
+ colorScheme: baseTheme.colorScheme,
50
+ colorVariables: baseTheme.colorVariables
51
+ };
52
+ setTheme(customTheme);
53
+ } else setTheme(theme);
54
+ };
42
55
  return /*#__PURE__*/ jsx(Component, {
43
56
  isOpen: isOpen,
44
57
  classNames: {
@@ -61,10 +74,17 @@ const UXThemeSwitchModal = ({ as = 'modal', omitThemes = [], backdrop, isOpen, o
61
74
  className: "grid grid-cols-3 gap-y-5 pt-1",
62
75
  children: themes.map((theme)=>/*#__PURE__*/ jsx(ThemeItem, {
63
76
  isSelected: selectedTheme.id === theme.id,
64
- onClick: ()=>setTheme(theme),
77
+ onClick: ()=>handleThemeSelect(theme),
65
78
  ...theme
66
79
  }, theme.id))
67
80
  }),
81
+ 'custom' === selectedTheme.id && /*#__PURE__*/ jsxs(Fragment, {
82
+ children: [
83
+ /*#__PURE__*/ jsx(UXDivider, {}),
84
+ /*#__PURE__*/ jsx(CustomThemeConfig, {}),
85
+ /*#__PURE__*/ jsx(UXDivider, {})
86
+ ]
87
+ }),
68
88
  /*#__PURE__*/ jsx(UXDivider, {}),
69
89
  /*#__PURE__*/ jsxs(VStack, {
70
90
  gap: "md",
@@ -1,11 +1,14 @@
1
- import { useTheme } from "./use-theme.js";
1
+ import { useMemo } from "react";
2
+ import { useThemeStore } from "./use-theme-store.js";
2
3
  const useColorScheme = ()=>{
3
- const { theme } = useTheme();
4
- const { colorScheme } = theme;
5
- return {
6
- colorScheme,
7
- isDark: 'dark' === colorScheme,
8
- isLight: 'light' === colorScheme
9
- };
4
+ const { colorScheme } = useThemeStore((state)=>state.theme);
5
+ const scheme = useMemo(()=>({
6
+ colorScheme,
7
+ isDark: 'dark' === colorScheme,
8
+ isLight: 'light' === colorScheme
9
+ }), [
10
+ colorScheme
11
+ ]);
12
+ return scheme;
10
13
  };
11
14
  export { useColorScheme };
@@ -1,10 +1,14 @@
1
- import { useTheme } from "./use-theme.js";
1
+ import { useMemo } from "react";
2
+ import { useThemeStore } from "./use-theme-store.js";
2
3
  const useThemeColor = ()=>{
3
- const { theme } = useTheme();
4
- return {
5
- ...theme.colorVariables,
6
- transparent: 'transparent',
7
- white: '#FFFFFF'
8
- };
4
+ const { colorVariables } = useThemeStore((state)=>state.theme);
5
+ const themeColor = useMemo(()=>({
6
+ ...colorVariables,
7
+ transparent: 'transparent',
8
+ white: '#FFFFFF'
9
+ }), [
10
+ colorVariables
11
+ ]);
12
+ return themeColor;
9
13
  };
10
14
  export { useThemeColor };
@@ -20,7 +20,7 @@ const useThemeStore = create()(persist((set)=>({
20
20
  })
21
21
  }), {
22
22
  name: 'ux-preferences-theme',
23
- version: 1,
23
+ version: 2,
24
24
  storage: createJSONStorage(()=>'undefined' != typeof window ? window.localStorage : {}),
25
25
  partialize: (state)=>({
26
26
  theme: state.theme,
@@ -1,5 +1,5 @@
1
1
  import { useEffect } from "react";
2
- import { themeKeys } from "@particle-network/ui-shared";
2
+ import { colorToCSSVariable, hexColorToHSLValue, themeKeys } from "@particle-network/ui-shared";
3
3
  import { useDebounceFn } from "ahooks";
4
4
  import { useThemeStore } from "./use-theme-store.js";
5
5
  const DEFAULT_FONT_FAMILY = 'Inter,system-ui,sans-serif,"Microsoft YaHei"';
@@ -14,13 +14,36 @@ const preloadFonts = ()=>{
14
14
  linkElement.href = PRELOAD_FONTS_URL;
15
15
  document.head.appendChild(linkElement);
16
16
  };
17
+ const applyCustomThemeColors = (theme)=>{
18
+ if ('undefined' == typeof window) return;
19
+ const root = document.documentElement;
20
+ Object.entries(theme.colorVariables).forEach(([color, value])=>{
21
+ const cssVariable = colorToCSSVariable[color]?.self;
22
+ if (cssVariable) cssVariable.forEach((variable)=>{
23
+ root.style.setProperty(variable, hexColorToHSLValue(value));
24
+ });
25
+ });
26
+ };
27
+ const clearCustomThemeColors = ()=>{
28
+ if ('undefined' == typeof window) return;
29
+ const root = document.documentElement;
30
+ Object.values(colorToCSSVariable).forEach((cssVar)=>{
31
+ cssVar.self?.forEach((variable)=>{
32
+ root.style.removeProperty(variable);
33
+ });
34
+ });
35
+ };
17
36
  const applyTheme = (theme)=>{
18
37
  if ('undefined' == typeof window) return;
19
38
  const root = document.documentElement;
20
39
  const isClassListCorrect = root.classList.contains(theme.id);
21
40
  const isDataThemeCorrect = root.getAttribute('data-theme') === theme.colorScheme;
22
41
  const isDataPrefersColorCorrect = root.getAttribute('data-prefers-color') === theme.colorScheme;
23
- if (isClassListCorrect && isDataThemeCorrect && isDataPrefersColorCorrect) return;
42
+ if ('custom' === theme.id) {
43
+ clearCustomThemeColors();
44
+ applyCustomThemeColors(theme);
45
+ } else clearCustomThemeColors();
46
+ if (isClassListCorrect && isDataThemeCorrect && isDataPrefersColorCorrect && 'custom' !== theme.id) return;
24
47
  if (!isDataThemeCorrect) root.setAttribute('data-theme', theme.colorScheme);
25
48
  if (!isDataPrefersColorCorrect) root.setAttribute('data-prefers-color', theme.colorScheme);
26
49
  root.classList.remove('dark');
@@ -29,7 +52,7 @@ const applyTheme = (theme)=>{
29
52
  themeKeys.forEach((key)=>{
30
53
  root.classList.remove(key);
31
54
  });
32
- root.classList.add(theme.id);
55
+ root.classList.add('custom' === theme.id ? theme.baseThemeId : theme.id);
33
56
  }
34
57
  };
35
58
  const extractFontFamilyFromLink = (link)=>{
@@ -97,6 +120,11 @@ const useTheme = ()=>{
97
120
  applyTheme(theme);
98
121
  debouncedApplyFont(fontUrl, theme.fontName);
99
122
  }, []);
123
+ useEffect(()=>{
124
+ applyTheme(theme);
125
+ }, [
126
+ theme
127
+ ]);
100
128
  const setTheme = (theme)=>{
101
129
  storeSetTheme(theme);
102
130
  applyTheme(theme);
@@ -1,31 +1,119 @@
1
1
  export declare const useI18n: () => {
2
- table: {
3
- emptyContent: string;
4
- };
5
- copy: {
6
- copy: string;
7
- success: string;
8
- error: string;
9
- address: string;
10
- };
11
- theme: {
12
- title: string;
13
- done: string;
14
- font: {
15
- title: string;
16
- placeholder: string;
17
- loading: string;
18
- success: string;
19
- error: string;
20
- hint: string;
21
- example: {
22
- show: string;
23
- hide: string;
2
+ readonly table: {
3
+ readonly emptyContent: "No data";
4
+ };
5
+ readonly copy: {
6
+ readonly copy: "Copy";
7
+ readonly success: "Copied successfully!";
8
+ readonly error: "Copy failed: ";
9
+ readonly address: "Address copied: ";
10
+ };
11
+ readonly theme: {
12
+ readonly title: "Theme";
13
+ readonly done: "Finish";
14
+ readonly font: {
15
+ readonly title: "Custom Font";
16
+ readonly placeholder: "Enter Google Fonts URL or custom font URL";
17
+ readonly loading: "Loading font...";
18
+ readonly success: "Font loaded: ";
19
+ readonly error: "Failed to load font";
20
+ readonly hint: "Try Google Fonts: ";
21
+ readonly example: {
22
+ readonly show: "Show example";
23
+ readonly hide: "Hide example";
24
+ };
25
+ };
26
+ readonly custom: {
27
+ readonly title: "Custom Theme";
28
+ readonly preset: "Theme Preset";
29
+ readonly categories: {
30
+ readonly basic: "Basic";
31
+ readonly background: "Background";
32
+ readonly text: "Text";
33
+ readonly signal: "Signal";
34
+ };
35
+ readonly colors: {
36
+ readonly primary: "Primary";
37
+ readonly bullish: "Positive";
38
+ readonly bearish: "Negative";
39
+ readonly 'bg-default': "Background";
40
+ readonly 'bg-200': "Background Quaternary";
41
+ readonly 'bg-300': "Background Tertiary";
42
+ readonly 'bg-400': "Background Secondary";
43
+ readonly overlay: "Overlay";
44
+ readonly default: "Default";
45
+ readonly foreground: "Text Primary";
46
+ readonly secondary: "Text Secondary";
47
+ readonly tertiary: "Text Tertiary";
48
+ readonly divider: "Divider";
49
+ readonly success: "Signal Green";
50
+ readonly danger: "Signal Red";
51
+ readonly alert: "Signal Orange";
52
+ readonly gold: "Signal Yellow";
53
+ };
54
+ };
55
+ };
56
+ readonly switch: {
57
+ readonly on: "On";
58
+ readonly off: "Off";
59
+ };
60
+ } | {
61
+ readonly table: {
62
+ readonly emptyContent: "暂无数据";
63
+ };
64
+ readonly copy: {
65
+ readonly copy: "复制";
66
+ readonly success: "复制成功!";
67
+ readonly error: "复制失败:";
68
+ readonly address: "地址已复制:";
69
+ };
70
+ readonly theme: {
71
+ readonly title: "主题";
72
+ readonly done: "完成";
73
+ readonly font: {
74
+ readonly title: "自定义字体";
75
+ readonly placeholder: "输入 Google Fonts 或自定义字体链接";
76
+ readonly loading: "正在加载字体...";
77
+ readonly success: "已加载字体:";
78
+ readonly error: "字体加载失败";
79
+ readonly hint: "参考 Google Fonts: ";
80
+ readonly example: {
81
+ readonly show: "显示示例";
82
+ readonly hide: "隐藏示例";
83
+ };
84
+ };
85
+ readonly custom: {
86
+ readonly title: "自定义主题";
87
+ readonly preset: "主题预设";
88
+ readonly categories: {
89
+ readonly basic: "基础";
90
+ readonly background: "背景";
91
+ readonly text: "文字";
92
+ readonly signal: "信号";
93
+ };
94
+ readonly colors: {
95
+ readonly primary: "主要色";
96
+ readonly bullish: "上涨色";
97
+ readonly bearish: "下跌色";
98
+ readonly 'bg-default': "一级背景色";
99
+ readonly 'bg-200': "四级背景色";
100
+ readonly 'bg-300': "三级背景色";
101
+ readonly 'bg-400': "二级背景色";
102
+ readonly overlay: "浮窗色";
103
+ readonly default: "默认";
104
+ readonly foreground: "一级文字色";
105
+ readonly secondary: "二级文字色";
106
+ readonly tertiary: "三级文字色";
107
+ readonly divider: "分割线";
108
+ readonly success: "绿色信号";
109
+ readonly danger: "红色信号";
110
+ readonly alert: "橙色信号";
111
+ readonly gold: "黄色信号";
24
112
  };
25
113
  };
26
114
  };
27
- switch: {
28
- on: string;
29
- off: string;
115
+ readonly switch: {
116
+ readonly on: "开";
117
+ readonly off: "关";
30
118
  };
31
119
  };
@@ -1,3 +1,4 @@
1
+ import { useMemo } from "react";
1
2
  import { useLang } from "./useLang.js";
2
3
  const en = {
3
4
  table: {
@@ -23,6 +24,35 @@ const en = {
23
24
  show: 'Show example',
24
25
  hide: 'Hide example'
25
26
  }
27
+ },
28
+ custom: {
29
+ title: 'Custom Theme',
30
+ preset: 'Theme Preset',
31
+ categories: {
32
+ basic: 'Basic',
33
+ background: 'Background',
34
+ text: 'Text',
35
+ signal: 'Signal'
36
+ },
37
+ colors: {
38
+ primary: 'Primary',
39
+ bullish: 'Positive',
40
+ bearish: 'Negative',
41
+ 'bg-default': 'Background',
42
+ 'bg-200': 'Background Quaternary',
43
+ 'bg-300': 'Background Tertiary',
44
+ 'bg-400': 'Background Secondary',
45
+ overlay: 'Overlay',
46
+ default: 'Default',
47
+ foreground: 'Text Primary',
48
+ secondary: 'Text Secondary',
49
+ tertiary: 'Text Tertiary',
50
+ divider: 'Divider',
51
+ success: 'Signal Green',
52
+ danger: 'Signal Red',
53
+ alert: 'Signal Orange',
54
+ gold: 'Signal Yellow'
55
+ }
26
56
  }
27
57
  },
28
58
  switch: {
@@ -54,6 +84,35 @@ const zh = {
54
84
  show: '显示示例',
55
85
  hide: '隐藏示例'
56
86
  }
87
+ },
88
+ custom: {
89
+ title: '自定义主题',
90
+ preset: '主题预设',
91
+ categories: {
92
+ basic: '基础',
93
+ background: '背景',
94
+ text: '文字',
95
+ signal: '信号'
96
+ },
97
+ colors: {
98
+ primary: '主要色',
99
+ bullish: '上涨色',
100
+ bearish: '下跌色',
101
+ 'bg-default': '一级背景色',
102
+ 'bg-200': '四级背景色',
103
+ 'bg-300': '三级背景色',
104
+ 'bg-400': '二级背景色',
105
+ overlay: '浮窗色',
106
+ default: '默认',
107
+ foreground: '一级文字色',
108
+ secondary: '二级文字色',
109
+ tertiary: '三级文字色',
110
+ divider: '分割线',
111
+ success: '绿色信号',
112
+ danger: '红色信号',
113
+ alert: '橙色信号',
114
+ gold: '黄色信号'
115
+ }
57
116
  }
58
117
  },
59
118
  switch: {
@@ -62,7 +121,10 @@ const zh = {
62
121
  }
63
122
  };
64
123
  const useI18n = ()=>{
65
- const lang = useLang();
66
- return 'zh' === lang ? zh : en;
124
+ const { lang } = useLang();
125
+ const i18n = useMemo(()=>'zh' === lang ? zh : en, [
126
+ lang
127
+ ]);
128
+ return i18n;
67
129
  };
68
130
  export { useI18n };
@@ -1,3 +1,7 @@
1
1
  type Language = 'zh' | 'en';
2
- export declare function useLang(): Language;
2
+ export declare function useLang(): {
3
+ lang: Language;
4
+ setLang: (lang: Language) => void;
5
+ toggleLang: () => void;
6
+ };
3
7
  export {};
@@ -23,6 +23,18 @@ function useLang() {
23
23
  observer.disconnect();
24
24
  };
25
25
  }, []);
26
- return lang;
26
+ const handleSetLang = (lang)=>{
27
+ setLang(lang);
28
+ document.documentElement.lang = lang;
29
+ };
30
+ const toggleLang = ()=>{
31
+ const newLang = 'zh' === lang ? 'en' : 'zh';
32
+ handleSetLang(newLang);
33
+ };
34
+ return {
35
+ lang,
36
+ setLang: handleSetLang,
37
+ toggleLang
38
+ };
27
39
  }
28
40
  export { useLang };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@particle-network/ui-react",
3
- "version": "0.5.1-beta.2",
3
+ "version": "0.5.1-beta.4",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -48,10 +48,11 @@
48
48
  "dependencies": {
49
49
  "ahooks": "^3.9.4",
50
50
  "copy-to-clipboard": "^3.3.3",
51
+ "immer": "^11.1.3",
51
52
  "react-aria-components": "^1.14.0",
52
53
  "zustand": "^5.0.8",
53
- "@particle-network/icons": "0.5.1-beta.0",
54
- "@particle-network/ui-shared": "0.4.0"
54
+ "@particle-network/ui-shared": "0.4.1-beta.0",
55
+ "@particle-network/icons": "0.5.1-beta.1"
55
56
  },
56
57
  "scripts": {
57
58
  "build": "rslib build",
@@ -1094,85 +1094,6 @@ module.exports = {
1094
1094
  },
1095
1095
  },
1096
1096
  },
1097
- 'product-test': {
1098
- extend: 'dark',
1099
- colors: {
1100
- default: {
1101
- DEFAULT: '#222D33',
1102
- foreground: '#F6FEFD',
1103
- },
1104
- secondary: {
1105
- DEFAULT: '#D1D4DC',
1106
- foreground: '#000000',
1107
- },
1108
- tertiary: {
1109
- DEFAULT: '#949E9C',
1110
- foreground: '#000000',
1111
- },
1112
- primary: {
1113
- 100: '#EFF5F5',
1114
- 200: '#CEE6E4',
1115
- 300: '#A8DBD6',
1116
- 400: '#7ED4CB',
1117
- 500: '#50D2C1',
1118
- 600: '#34B9A5',
1119
- 700: '#2B8C7C',
1120
- 800: '#206255',
1121
- 900: '#143831',
1122
- DEFAULT: '#50D2C1',
1123
- foreground: '#000000',
1124
- },
1125
- success: {
1126
- DEFAULT: '#19AB5E',
1127
- foreground: '#000000',
1128
- },
1129
- danger: {
1130
- DEFAULT: '#E84A5A',
1131
- foreground: '#000000',
1132
- },
1133
- alert: {
1134
- DEFAULT: '#F57733',
1135
- foreground: '#000000',
1136
- },
1137
- warning: {
1138
- DEFAULT: '#FFD13F',
1139
- foreground: '#000000',
1140
- },
1141
- gold: {
1142
- DEFAULT: '#FFB800',
1143
- foreground: '#000000',
1144
- },
1145
- overlay: {
1146
- DEFAULT: '#1B2429',
1147
- foreground: '#FFFFFF',
1148
- },
1149
- divider: {
1150
- DEFAULT: '#394145',
1151
- foreground: '#FFFFFF',
1152
- },
1153
- bullish: {
1154
- DEFAULT: '#D745FF',
1155
- foreground: '#000000',
1156
- },
1157
- bearish: {
1158
- DEFAULT: '#F38300',
1159
- foreground: '#000000',
1160
- },
1161
- background: {
1162
- 200: '#222D33',
1163
- 300: '#1B2429',
1164
- 400: '#0F1A1F',
1165
- 500: '#0A1318',
1166
- DEFAULT: '#0A1318',
1167
- },
1168
- foreground: {
1169
- 100: '#949E9C',
1170
- 300: '#D1D4DC',
1171
- 500: '#F6FEFD',
1172
- DEFAULT: '#F6FEFD',
1173
- },
1174
- },
1175
- },
1176
1097
  },
1177
1098
  function({ addVariant, addComponents }) {
1178
1099
  addVariant('child', '& > *');