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

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.
@@ -0,0 +1,40 @@
1
+ import { type ThemeItemType } from './theme-data';
2
+ interface State {
3
+ /**
4
+ * 临时选中的 theme(用于预览)
5
+ */
6
+ selectedTheme: ThemeItemType;
7
+ /**
8
+ * 保存的主题
9
+ */
10
+ savedTheme: ThemeItemType;
11
+ /**
12
+ * 临时输入的字体链接(用于预览)
13
+ */
14
+ selectedFontLink: string;
15
+ /**
16
+ * 保存的字体链接
17
+ */
18
+ savedFontLink: string;
19
+ }
20
+ interface Actions {
21
+ setSelectedTheme: (theme: ThemeItemType) => void;
22
+ setSavedTheme: (theme: ThemeItemType) => void;
23
+ setSelectedFontLink: (link: string) => void;
24
+ setSavedFontLink: (link: string) => void;
25
+ }
26
+ type ThemeStore = State & Actions;
27
+ export declare const useThemeStore: import("zustand").UseBoundStore<Omit<import("zustand").StoreApi<ThemeStore>, "setState" | "persist"> & {
28
+ setState(partial: ThemeStore | Partial<ThemeStore> | ((state: ThemeStore) => ThemeStore | Partial<ThemeStore>), replace?: false | undefined): unknown;
29
+ setState(state: ThemeStore | ((state: ThemeStore) => ThemeStore), replace: true): unknown;
30
+ persist: {
31
+ setOptions: (options: Partial<import("zustand/middleware").PersistOptions<ThemeStore, unknown, unknown>>) => void;
32
+ clearStorage: () => void;
33
+ rehydrate: () => Promise<void> | void;
34
+ hasHydrated: () => boolean;
35
+ onHydrate: (fn: (state: ThemeStore) => void) => () => void;
36
+ onFinishHydration: (fn: (state: ThemeStore) => void) => () => void;
37
+ getOptions: () => Partial<import("zustand/middleware").PersistOptions<ThemeStore, unknown, unknown>>;
38
+ };
39
+ }>;
40
+ export {};
@@ -0,0 +1,25 @@
1
+ import { create } from "zustand";
2
+ import { createJSONStorage, persist } from "zustand/middleware";
3
+ import { themeData } from "./theme-data.js";
4
+ const useThemeStore = create()(persist((set)=>({
5
+ selectedTheme: themeData["0"],
6
+ savedTheme: themeData["0"],
7
+ selectedFontLink: '',
8
+ savedFontLink: '',
9
+ setSelectedTheme: (theme)=>set({
10
+ selectedTheme: theme
11
+ }),
12
+ setSavedTheme: (theme)=>set({
13
+ savedTheme: theme
14
+ }),
15
+ setSelectedFontLink: (link)=>set({
16
+ selectedFontLink: link
17
+ }),
18
+ setSavedFontLink: (link)=>set({
19
+ savedFontLink: link
20
+ })
21
+ }), {
22
+ name: 'ux-theme',
23
+ storage: createJSONStorage(()=>'undefined' != typeof window ? window.localStorage : {})
24
+ }));
25
+ export { useThemeStore };
@@ -0,0 +1,14 @@
1
+ import { type ThemeItemType } from './theme-data';
2
+ /**
3
+ * UX 主题管理 Hook
4
+ */
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;
14
+ };
@@ -0,0 +1,90 @@
1
+ import { useEffect } from "react";
2
+ import { themeKeys } from "./theme-data.js";
3
+ import { useThemeStore } from "./use-theme-store.js";
4
+ 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 applyTheme = (theme)=>{
6
+ if ('undefined' == typeof window) return;
7
+ const root = document.documentElement;
8
+ root.setAttribute('data-theme', theme.colorScheme);
9
+ root.setAttribute('data-prefers-color', theme.colorScheme);
10
+ themeKeys.forEach((key)=>{
11
+ root.classList.remove(key);
12
+ });
13
+ root.classList.add(theme.key);
14
+ };
15
+ const extractFontFamilyFromLink = (link)=>{
16
+ if (!link) return null;
17
+ try {
18
+ const url = new URL(link);
19
+ const familyParam = url.searchParams.get('family');
20
+ if (!familyParam) return null;
21
+ const fontName = familyParam.split(':')[0].replace(/\+/g, ' ');
22
+ return fontName;
23
+ } catch {
24
+ return null;
25
+ }
26
+ };
27
+ const loadGoogleFont = (link)=>{
28
+ if ('undefined' == typeof window) return;
29
+ const existingLink = document.getElementById('ux-google-font-link');
30
+ if (existingLink) existingLink.remove();
31
+ if (!link?.trim()) {
32
+ document.documentElement.style.setProperty('--ux-font-family', DEFAULT_FONT_FAMILY);
33
+ document.body.style.fontFamily = DEFAULT_FONT_FAMILY;
34
+ return;
35
+ }
36
+ const linkElement = document.createElement('link');
37
+ linkElement.id = 'ux-google-font-link';
38
+ linkElement.rel = 'stylesheet';
39
+ linkElement.href = link;
40
+ 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
+ };
47
+ const useTheme = ()=>{
48
+ const selectedTheme = useThemeStore((state)=>state.selectedTheme);
49
+ const savedTheme = useThemeStore((state)=>state.savedTheme);
50
+ const selectedFontLink = useThemeStore((state)=>state.selectedFontLink);
51
+ const savedFontLink = useThemeStore((state)=>state.savedFontLink);
52
+ const storeSetSelectedTheme = useThemeStore((state)=>state.setSelectedTheme);
53
+ const storeSetSavedTheme = useThemeStore((state)=>state.setSavedTheme);
54
+ const storeSetSelectedFontLink = useThemeStore((state)=>state.setSelectedFontLink);
55
+ const storeSetSavedFontLink = useThemeStore((state)=>state.setSavedFontLink);
56
+ useEffect(()=>{
57
+ applyTheme(savedTheme);
58
+ loadGoogleFont(savedFontLink);
59
+ }, [
60
+ savedTheme,
61
+ savedFontLink
62
+ ]);
63
+ const setSelectedTheme = (theme)=>{
64
+ storeSetSelectedTheme(theme);
65
+ applyTheme(theme);
66
+ };
67
+ const setSavedTheme = (theme)=>{
68
+ storeSetSavedTheme(theme);
69
+ applyTheme(theme);
70
+ };
71
+ const setSelectedFontLink = (link)=>{
72
+ storeSetSelectedFontLink(link);
73
+ loadGoogleFont(link);
74
+ };
75
+ const setSavedFontLink = (link)=>{
76
+ storeSetSavedFontLink(link);
77
+ loadGoogleFont(link);
78
+ };
79
+ return {
80
+ selectedTheme,
81
+ setSelectedTheme,
82
+ savedTheme,
83
+ setSavedTheme,
84
+ selectedFontLink,
85
+ setSelectedFontLink,
86
+ savedFontLink,
87
+ setSavedFontLink
88
+ };
89
+ };
90
+ export { useTheme };
@@ -24,5 +24,6 @@ export * from './UXSwitch';
24
24
  export * from './UXTable';
25
25
  export * from './UXTabs';
26
26
  export * from './UXTextarea';
27
+ export * from './UXThemeSwitch';
27
28
  export * from './UXToast';
28
29
  export * from './UXTooltip';
@@ -24,5 +24,6 @@ export * from "./UXSwitch/index.js";
24
24
  export * from "./UXTable/index.js";
25
25
  export * from "./UXTabs/index.js";
26
26
  export * from "./UXTextarea/index.js";
27
+ export * from "./UXThemeSwitch/index.js";
27
28
  export * from "./UXToast/index.js";
28
29
  export * from "./UXTooltip/index.js";
@@ -8,4 +8,13 @@ export declare const useI18n: () => {
8
8
  error: string;
9
9
  address: string;
10
10
  };
11
+ theme: {
12
+ title: string;
13
+ reset: string;
14
+ apply: string;
15
+ font: {
16
+ title: string;
17
+ placeholder: string;
18
+ };
19
+ };
11
20
  };
@@ -8,6 +8,15 @@ const en = {
8
8
  success: 'Copied successfully!',
9
9
  error: 'Copy failed: ',
10
10
  address: 'Address copied: '
11
+ },
12
+ theme: {
13
+ title: 'Theme',
14
+ reset: 'Reset',
15
+ apply: 'Apply Theme',
16
+ font: {
17
+ title: 'Custom Font',
18
+ placeholder: 'Enter Google Fonts link'
19
+ }
11
20
  }
12
21
  };
13
22
  const zh = {
@@ -19,6 +28,15 @@ const zh = {
19
28
  success: '复制成功!',
20
29
  error: '复制失败:',
21
30
  address: '地址已复制:'
31
+ },
32
+ theme: {
33
+ title: '主题',
34
+ reset: '重置',
35
+ apply: '应用主题',
36
+ font: {
37
+ title: '自定义字体',
38
+ placeholder: '输入 Google Fonts 链接'
39
+ }
22
40
  }
23
41
  };
24
42
  const useI18n = ()=>{
@@ -263,38 +263,6 @@ export declare const inputClasses: {
263
263
  labelPlacement?: undefined;
264
264
  radius?: undefined;
265
265
  isMultiline?: undefined;
266
- } | {
267
- isInvalid: boolean;
268
- variant: string;
269
- class: {
270
- inputWrapper: string[];
271
- label?: undefined;
272
- input?: undefined;
273
- innerWrapper?: undefined;
274
- stepperButton?: undefined;
275
- };
276
- size?: undefined;
277
- color?: undefined;
278
- labelPlacement?: undefined;
279
- radius?: undefined;
280
- disableAnimation?: undefined;
281
- isMultiline?: undefined;
282
- } | {
283
- isInvalid: boolean;
284
- variant: string;
285
- class: {
286
- inputWrapper: string;
287
- label?: undefined;
288
- input?: undefined;
289
- innerWrapper?: undefined;
290
- stepperButton?: undefined;
291
- };
292
- size?: undefined;
293
- color?: undefined;
294
- labelPlacement?: undefined;
295
- radius?: undefined;
296
- disableAnimation?: undefined;
297
- isMultiline?: undefined;
298
266
  } | {
299
267
  labelPlacement: string;
300
268
  size: string;
@@ -1,7 +1,7 @@
1
1
  const inputClasses = {
2
2
  defaultVariants: {
3
3
  variant: 'flat',
4
- color: 'primary',
4
+ color: 'default',
5
5
  size: 'md',
6
6
  fullWidth: true,
7
7
  labelPlacement: 'outside',
@@ -75,19 +75,19 @@ const inputClasses = {
75
75
  size: {
76
76
  sm: {
77
77
  label: 'text-tiny text-foreground-300',
78
- inputWrapper: 'text-tiny h-6 min-h-6 px-2 rounded-small',
78
+ inputWrapper: 'text-tiny h-6 min-h-6 px-2.5 rounded-small',
79
79
  input: 'text-tiny',
80
80
  clearButton: 'text-medium'
81
81
  },
82
82
  md: {
83
83
  label: 'text-tiny text-foreground-300',
84
- inputWrapper: 'text-tiny h-[30px] min-h-[30px] rounded-small',
84
+ inputWrapper: 'text-tiny h-[30px] min-h-[30px] rounded-small px-2.5',
85
85
  input: 'text-tiny',
86
86
  clearButton: 'text-large'
87
87
  },
88
88
  lg: {
89
89
  label: 'text-small text-foreground-300',
90
- inputWrapper: 'text-small h-11 min-h-11 rounded-medium',
90
+ inputWrapper: 'text-small h-11 min-h-11 rounded-medium px-2.5',
91
91
  input: 'text-small',
92
92
  clearButton: 'text-large'
93
93
  }
@@ -141,7 +141,7 @@ const inputClasses = {
141
141
  },
142
142
  isInvalid: {
143
143
  true: {
144
- inputWrapper: 'group-data-[focus=true]:border-danger caret-danger',
144
+ inputWrapper: 'border border-danger caret-danger !bg-background group-data-[focus=true]:!bg-background group-data-[hover=true]:!bg-background',
145
145
  label: 'text-danger',
146
146
  input: 'placeholder:text-danger text-danger'
147
147
  }
@@ -389,24 +389,6 @@ const inputClasses = {
389
389
  inputWrapper: 'transition-colors motion-reduce:transition-none'
390
390
  }
391
391
  },
392
- {
393
- isInvalid: true,
394
- variant: 'flat',
395
- class: {
396
- inputWrapper: [
397
- '!bg-danger-50',
398
- 'data-[hover=true]:!bg-danger-100',
399
- 'group-data-[focus=true]:!bg-danger-50'
400
- ]
401
- }
402
- },
403
- {
404
- isInvalid: true,
405
- variant: 'bordered',
406
- class: {
407
- inputWrapper: '!border-danger group-data-[focus=true]:!border-danger'
408
- }
409
- },
410
392
  {
411
393
  labelPlacement: 'inside',
412
394
  size: 'sm',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@particle-network/ui-react",
3
- "version": "0.3.2",
3
+ "version": "0.4.0-beta.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -49,8 +49,9 @@
49
49
  "dependencies": {
50
50
  "ahooks": "^3.9.4",
51
51
  "copy-to-clipboard": "^3.3.3",
52
- "@particle-network/ui-shared": "0.2.0",
53
- "@particle-network/icons": "0.3.1"
52
+ "zustand": "^5.0.8",
53
+ "@particle-network/icons": "0.3.1",
54
+ "@particle-network/ui-shared": "0.2.0"
54
55
  },
55
56
  "scripts": {
56
57
  "build": "rslib build",