@tais00/theme 0.7.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-present, Lexmin0412.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,151 @@
1
+ # `@dify-chat/theme`
2
+
3
+ ![version](https://img.shields.io/npm/v/@dify-chat/theme) ![NPM Last Update](https://img.shields.io/npm/last-update/@dify-chat/theme) ![NPM Downloads](https://img.shields.io/npm/dm/@dify-chat/theme)
4
+
5
+ `@dify-chat/theme` 是 Dify Chat 项目的主题包,它提供了一套主题管理的完整方案,包括主题上下文 hook、主题切换组件、主题状态管理等。
6
+
7
+ ## 主要功能
8
+
9
+ - 主题模式切换(系统/浅色/深色)
10
+ - 完整的主题上下文管理
11
+ - 暗黑模式自动适配
12
+
13
+ ## 安装
14
+
15
+ 通过 npm/yarn/pnpm 安装:
16
+
17
+ ```bash
18
+ # npm
19
+ npm install @dify-chat/theme
20
+
21
+ # yarn
22
+ yarn add @dify-chat/theme
23
+
24
+ # pnpm
25
+ pnpm add @dify-chat/theme
26
+ ```
27
+
28
+ ## API
29
+
30
+ ### `<ThemeContextProvider />`
31
+
32
+ 主题上下文容器。
33
+
34
+ > 说明:只有在上层组件使用了 `ThemeContextProvider` 包裹应用,才能在子组件中使用主题相关的功能。
35
+
36
+ 在最外层组件中使用 `ThemeContextProvider` 包裹应用:
37
+
38
+ ```tsx
39
+ import { ThemeContextProvider } from '@dify-chat/theme'
40
+ function App() {
41
+ return (
42
+ <ThemeContextProvider>
43
+ <YourApp />
44
+ </ThemeContextProvider>
45
+ )
46
+ }
47
+ ```
48
+
49
+ ### `<ThemeSelector />`
50
+
51
+ 主题选择器组件。
52
+
53
+ 默认情况下,`ThemeContextProvider` 中已经提供了自适应系统主题的能力。如果你需要支持用户手动切换主题模式,可以引入主题选择器:
54
+
55
+ ```tsx
56
+ import { ThemeSelector } from '@dify-chat/theme'
57
+
58
+ function App() {
59
+ const { themeMode } = useThemeContext()
60
+ return (
61
+ <ThemeSelector>
62
+ <Button>当前主题模式:{themeMode}</Button>
63
+ </ThemeSelector>
64
+ )
65
+ }
66
+ ```
67
+
68
+ ### `useThemeContext()`
69
+
70
+ 获取主题上下文 hook。
71
+
72
+ 返回值:
73
+
74
+ - `theme`: 当前应用的主题,值为 `'light' | 'dark'`
75
+ - `themeMode`: 当前主题模式,值为 `'light' | 'dark' | 'system'`
76
+ - `setThemeMode`: 设置主题模式,接受一个 `'light' | 'dark' | 'system'` 类型的参数
77
+
78
+ 你可以使用 `useThemeContext` hook 获取当前应用的主题:
79
+
80
+ ```tsx
81
+ import { useThemeContext } from '@dify-chat/theme'
82
+
83
+ function ThemeToggle() {
84
+ const { theme } = useThemeContext()
85
+
86
+ return <div>当前主题模式:{theme}</div>
87
+ }
88
+ ```
89
+
90
+ 也可以在组件中使用 `setThemeMode` 方法,自定义切换主题模式:
91
+
92
+ ```tsx
93
+ import { useThemeContext } from '@dify-chat/theme'
94
+
95
+ function ThemeSwitcher() {
96
+ const { themeMode, setThemeMode } = useThemeContext()
97
+ return (
98
+ <div>
99
+ <h3>当前主题模式:{themeMode}</h3>
100
+
101
+ <div>
102
+ <Button onClick={() => setThemeMode('light')}>浅色模式</Button>
103
+ <Button onClick={() => setThemeMode('dark')}>深色模式</Button>
104
+ <Button onClick={() => setThemeMode('system')}>系统主题</Button>
105
+ </div>
106
+ </div>
107
+ )
108
+ }
109
+ ```
110
+
111
+ ### 其他导出的成员
112
+
113
+ **枚举**
114
+
115
+ - `ThemeEnum`: 主题枚举
116
+ - `ThemeModeEnum`: 主题模式枚举
117
+ - `ThemeModeLabelEnum`: 主题模式文本枚举
118
+
119
+ **常量**
120
+
121
+ - `ThemeModeOptions` : 主题模式选项
122
+
123
+ **类型**
124
+
125
+ - `IThemeContext`: 主题上下文类型
126
+ - `IThemeMode`: 主题模式类型
127
+ - `ICurrentTheme`: 当前主题类型
128
+
129
+ 使用示例:
130
+
131
+ ```tsx
132
+ import { Select } from 'antd'
133
+ import { ThemeModeEnum, ThemeModeOptions } from '@dify-chat/theme'
134
+
135
+ function ThemeSwitcher() {
136
+ const { themeMode, setThemeMode } = useThemeContext()
137
+ return (
138
+ <div>
139
+ <h3>当前主题模式:{themeMode}</h3>
140
+ <div>
141
+ 切换主题模式:
142
+ <Select
143
+ options={ThemeModeOptions}
144
+ value={themeMode}
145
+ onChange={value => setThemeMode(value as ThemeModeEnum)}
146
+ />
147
+ </div>
148
+ </div>
149
+ )
150
+ }
151
+ ```
@@ -0,0 +1 @@
1
+ export { default as ThemeSelector } from './theme-selector';
@@ -0,0 +1,2 @@
1
+ import theme_selector from "./theme-selector.js";
2
+ export { theme_selector as ThemeSelector };
@@ -0,0 +1,8 @@
1
+ interface IThemeSelectorProps {
2
+ children?: React.ReactNode;
3
+ }
4
+ /**
5
+ * 主题选择器组件
6
+ */
7
+ export default function ThemeSelector(props: IThemeSelectorProps): import("react/jsx-runtime").JSX.Element;
8
+ export {};
@@ -0,0 +1,48 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { Dropdown } from "antd";
3
+ import { useThemeContext } from "../hooks/index.js";
4
+ import { ThemeModeEnum, ThemeModeLabelEnum } from "../constants/index.js";
5
+ import { DynamicIcon } from "lucide-react/dynamic";
6
+ function ThemeSelector(props) {
7
+ const { children } = props;
8
+ const { themeMode, setThemeMode } = useThemeContext();
9
+ return /*#__PURE__*/ jsx(Dropdown, {
10
+ placement: "bottomRight",
11
+ menu: {
12
+ selectedKeys: [
13
+ themeMode
14
+ ],
15
+ items: [
16
+ {
17
+ type: 'item',
18
+ key: ThemeModeEnum.SYSTEM,
19
+ label: ThemeModeLabelEnum.SYSTEM,
20
+ icon: /*#__PURE__*/ jsx(DynamicIcon, {
21
+ name: "screen-share"
22
+ })
23
+ },
24
+ {
25
+ type: 'item',
26
+ key: ThemeModeEnum.LIGHT,
27
+ label: ThemeModeLabelEnum.LIGHT,
28
+ icon: /*#__PURE__*/ jsx(DynamicIcon, {
29
+ name: "sun"
30
+ })
31
+ },
32
+ {
33
+ type: 'item',
34
+ key: ThemeModeEnum.DARK,
35
+ label: ThemeModeLabelEnum.DARK,
36
+ icon: /*#__PURE__*/ jsx(DynamicIcon, {
37
+ name: "moon-star"
38
+ })
39
+ }
40
+ ],
41
+ onClick: (item)=>{
42
+ setThemeMode(item.key);
43
+ }
44
+ },
45
+ children: children
46
+ });
47
+ }
48
+ export { ThemeSelector as default };
@@ -0,0 +1,30 @@
1
+ /**
2
+ * 主题枚举
3
+ */
4
+ export declare enum ThemeEnum {
5
+ LIGHT = "light",
6
+ DARK = "dark"
7
+ }
8
+ /**
9
+ * 主题模式枚举
10
+ */
11
+ export declare enum ThemeModeEnum {
12
+ SYSTEM = "system",
13
+ LIGHT = "light",
14
+ DARK = "dark"
15
+ }
16
+ /**
17
+ * 主题模式文本枚举
18
+ */
19
+ export declare enum ThemeModeLabelEnum {
20
+ SYSTEM = "\u8DDF\u968F\u7CFB\u7EDF",
21
+ LIGHT = "\u6D45\u8272",
22
+ DARK = "\u6DF1\u8272"
23
+ }
24
+ /**
25
+ * 主题模式常量对应的选项
26
+ */
27
+ export declare const ThemeModeOptions: {
28
+ label: ThemeModeLabelEnum;
29
+ value: ThemeModeEnum;
30
+ }[];
@@ -0,0 +1,32 @@
1
+ var constants_ThemeEnum = /*#__PURE__*/ function(ThemeEnum) {
2
+ ThemeEnum["LIGHT"] = "light";
3
+ ThemeEnum["DARK"] = "dark";
4
+ return ThemeEnum;
5
+ }({});
6
+ var constants_ThemeModeEnum = /*#__PURE__*/ function(ThemeModeEnum) {
7
+ ThemeModeEnum["SYSTEM"] = "system";
8
+ ThemeModeEnum["LIGHT"] = "light";
9
+ ThemeModeEnum["DARK"] = "dark";
10
+ return ThemeModeEnum;
11
+ }({});
12
+ var constants_ThemeModeLabelEnum = /*#__PURE__*/ function(ThemeModeLabelEnum) {
13
+ ThemeModeLabelEnum["SYSTEM"] = "\u8DDF\u968F\u7CFB\u7EDF";
14
+ ThemeModeLabelEnum["LIGHT"] = "\u6D45\u8272";
15
+ ThemeModeLabelEnum["DARK"] = "\u6DF1\u8272";
16
+ return ThemeModeLabelEnum;
17
+ }({});
18
+ const ThemeModeOptions = [
19
+ {
20
+ label: "\u8DDF\u968F\u7CFB\u7EDF",
21
+ value: "system"
22
+ },
23
+ {
24
+ label: "\u6D45\u8272",
25
+ value: "light"
26
+ },
27
+ {
28
+ label: "\u6DF1\u8272",
29
+ value: "dark"
30
+ }
31
+ ];
32
+ export { constants_ThemeEnum as ThemeEnum, constants_ThemeModeEnum as ThemeModeEnum, constants_ThemeModeLabelEnum as ThemeModeLabelEnum, ThemeModeOptions };
@@ -0,0 +1,61 @@
1
+ import React from 'react';
2
+ import { ThemeEnum, ThemeModeEnum } from '../constants';
3
+ /**
4
+ * 主题模式,用于用户手动切换, light-固定浅色 dark-固定深色,system-跟随系统
5
+ */
6
+ export type IThemeMode = 'light' | 'dark' | 'system';
7
+ /**
8
+ * 实际应用的主题
9
+ */
10
+ export type ICurrentTheme = 'light' | 'dark';
11
+ /**
12
+ * 主题上下文类型定义
13
+ */
14
+ export interface IThemeContext {
15
+ /**
16
+ * 当前主题
17
+ */
18
+ theme: ThemeEnum;
19
+ /**
20
+ * 当前主题模式
21
+ */
22
+ themeMode: ThemeModeEnum;
23
+ /**
24
+ * 手动设置主题模式
25
+ */
26
+ setThemeMode: (theme: ThemeModeEnum) => void;
27
+ }
28
+ /**
29
+ * 主题上下文
30
+ */
31
+ export declare const ThemeContext: React.Context<IThemeContext>;
32
+ /**
33
+ * 暗黑模式的 body 类名
34
+ */
35
+ export declare const DARK_CLASS_NAME = "dark";
36
+ /**
37
+ * 主题上下文提供者
38
+ */
39
+ export declare const ThemeContextProvider: (props: {
40
+ children: React.ReactNode;
41
+ }) => import("react/jsx-runtime").JSX.Element;
42
+ /**
43
+ * 获取主题上下文 hook
44
+ */
45
+ export declare const useThemeContext: () => {
46
+ isDark: boolean;
47
+ isLight: boolean;
48
+ isSystemMode: boolean;
49
+ /**
50
+ * 当前主题
51
+ */
52
+ theme: ThemeEnum;
53
+ /**
54
+ * 当前主题模式
55
+ */
56
+ themeMode: ThemeModeEnum;
57
+ /**
58
+ * 手动设置主题模式
59
+ */
60
+ setThemeMode: (theme: ThemeModeEnum) => void;
61
+ };
@@ -0,0 +1,77 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import react, { useCallback, useEffect, useState } from "react";
3
+ import { ThemeEnum, ThemeModeEnum } from "../constants/index.js";
4
+ import { LocalStorageKeys, LocalStorageStore } from "@dify-chat/helpers";
5
+ const ThemeContext = /*#__PURE__*/ react.createContext({
6
+ theme: ThemeEnum.LIGHT,
7
+ setThemeMode: ()=>{},
8
+ themeMode: ThemeModeEnum.SYSTEM
9
+ });
10
+ const DARK_CLASS_NAME = 'dark';
11
+ const ThemeContextProvider = (props)=>{
12
+ const { children } = props;
13
+ const [themeMode, setThemeMode] = useState(()=>{
14
+ if ('undefined' == typeof window) return ThemeModeEnum.SYSTEM;
15
+ return LocalStorageStore.get(LocalStorageKeys.THEME_MODE) || ThemeModeEnum.SYSTEM;
16
+ });
17
+ const [themeState, setThemeState] = react.useState(()=>{
18
+ if ('undefined' == typeof window) return ThemeEnum.LIGHT;
19
+ return LocalStorageStore.get(LocalStorageKeys.THEME) || ThemeEnum.LIGHT;
20
+ });
21
+ useEffect(()=>{
22
+ LocalStorageStore.set(LocalStorageKeys.THEME, themeState);
23
+ }, [
24
+ themeState
25
+ ]);
26
+ const handleColorSchemeChange = useCallback((event)=>{
27
+ if (event.matches) {
28
+ setThemeState(ThemeEnum.DARK);
29
+ document.body.classList.add(DARK_CLASS_NAME);
30
+ } else {
31
+ setThemeState(ThemeEnum.LIGHT);
32
+ document.body.classList.remove(DARK_CLASS_NAME);
33
+ }
34
+ }, [
35
+ setThemeState,
36
+ themeMode
37
+ ]);
38
+ useEffect(()=>{
39
+ LocalStorageStore.set(LocalStorageKeys.THEME_MODE, themeMode);
40
+ const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
41
+ if (themeMode === ThemeModeEnum.SYSTEM) {
42
+ handleColorSchemeChange(mediaQuery);
43
+ if (mediaQuery.addEventListener) mediaQuery.addEventListener('change', handleColorSchemeChange);
44
+ else if (mediaQuery.addListener) mediaQuery.addListener(handleColorSchemeChange);
45
+ } else {
46
+ if (mediaQuery.removeEventListener) mediaQuery.removeEventListener('change', handleColorSchemeChange);
47
+ else if (mediaQuery.removeListener) mediaQuery.removeListener(handleColorSchemeChange);
48
+ if (themeMode === ThemeModeEnum.DARK) {
49
+ setThemeState(ThemeEnum.DARK);
50
+ document.body.classList.add(DARK_CLASS_NAME);
51
+ } else if (themeMode === ThemeModeEnum.LIGHT) {
52
+ setThemeState(ThemeEnum.LIGHT);
53
+ document.body.classList.remove(DARK_CLASS_NAME);
54
+ }
55
+ }
56
+ }, [
57
+ themeMode
58
+ ]);
59
+ return /*#__PURE__*/ jsx(ThemeContext.Provider, {
60
+ value: {
61
+ theme: themeState,
62
+ themeMode,
63
+ setThemeMode
64
+ },
65
+ children: children
66
+ });
67
+ };
68
+ const useThemeContext = ()=>{
69
+ const context = react.useContext(ThemeContext);
70
+ return {
71
+ ...context,
72
+ isDark: context.theme === ThemeEnum.DARK,
73
+ isLight: context.theme === ThemeEnum.LIGHT,
74
+ isSystemMode: context.themeMode === ThemeModeEnum.SYSTEM
75
+ };
76
+ };
77
+ export { DARK_CLASS_NAME, ThemeContext, ThemeContextProvider, useThemeContext };
@@ -0,0 +1,3 @@
1
+ export * from './components';
2
+ export * from './constants';
3
+ export * from './hooks';
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export * from "./components/index.js";
2
+ export * from "./constants/index.js";
3
+ export * from "./hooks/index.js";
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@tais00/theme",
3
+ "version": "0.7.0",
4
+ "author": {
5
+ "name": "lexmin0412",
6
+ "email": "zhangle_dev@outlook.com",
7
+ "url": "http://github.com/lexmin0412"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/lexmin0412/dify-chat.git",
12
+ "directory": "packages/theme"
13
+ },
14
+ "source": "./src/index.ts",
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "type": "module",
19
+ "types": "./dist/index.d.ts",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "source": "./src/index.ts",
24
+ "import": "./dist/index.js"
25
+ }
26
+ },
27
+ "publishConfig": {
28
+ "access": "public",
29
+ "registry": "https://registry.npmjs.org"
30
+ },
31
+ "dependencies": {
32
+ "@tais00/helpers": "^0.7.0"
33
+ },
34
+ "devDependencies": {
35
+ "@eslint/js": "^9.24.0",
36
+ "@rsbuild/plugin-react": "^1.2.0",
37
+ "@rslib/core": "^0.12.4",
38
+ "@types/react": "^19.2.7",
39
+ "antd": "^6.0.0",
40
+ "eslint": "^9.26.0",
41
+ "globals": "^16.0.0",
42
+ "lucide-react": "^0.508.0",
43
+ "prettier": "^3.5.3",
44
+ "react": "^19.2.3",
45
+ "react-dom": "^19.2.3",
46
+ "typescript": "^5.9.3",
47
+ "typescript-eslint": "^8.30.1"
48
+ },
49
+ "peerDependencies": {
50
+ "antd": ">=5.0.0",
51
+ "lucide-react": "^0.508.0",
52
+ "react": ">=16.9.0",
53
+ "react-dom": ">=16.9.0"
54
+ },
55
+ "scripts": {
56
+ "build": "rslib build",
57
+ "dev": "rslib build --watch",
58
+ "format": "prettier --write .",
59
+ "lint": "eslint ."
60
+ }
61
+ }