Skip to content

Commit a7a72e2

Browse files
committed
feat: add custom theme color
1 parent 4d622ab commit a7a72e2

9 files changed

Lines changed: 174 additions & 13 deletions

File tree

src/components/AddModal/AddModal.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -297,10 +297,10 @@ const AddModal: React.FC = () => {
297297
{/* 拖拽遮罩层 */}
298298
{isDragging && (
299299
<Box className="fixed inset-0 z-[9999] bg-[rgba(25,118,210,0.15)] backdrop-blur-sm flex flex-col items-center justify-center pointer-events-none">
300-
<CloudUploadIcon className="text-[80px] text-[#1976d2] mb-2 opacity-90" />
300+
<CloudUploadIcon className="text-[80px] text-[--mui-palette-primary-main] mb-2 opacity-90" />
301301
<Typography
302302
variant="h5"
303-
className="text-2xl font-semibold text-[#1976d2] text-center opacity-90"
303+
className="text-2xl font-semibold text-[--mui-palette-primary-main] text-center opacity-90"
304304
>
305305
{t("components.AddModal.dragDropHere", "拖拽文件到这里")}
306306
</Typography>

src/components/Windows.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ const UpdateModal: React.FC<UpdateModalProps> = ({ open, onClose, update }) => {
103103
}
104104
}}
105105
style={{
106-
color: "#1976d2",
106+
color: "--mui-palette-primary-main",
107107
textDecoration: "underline",
108108
cursor: "pointer",
109109
}}
@@ -326,7 +326,7 @@ const WindowsHandler: React.FC = () => {
326326

327327
useEffect(() => {
328328
const w = getCurrentWindow();
329-
let unlisten = () => { };
329+
let unlisten = () => {};
330330
// 拦截关闭
331331
// onCloseRequested API provides preventDefault
332332
w.onCloseRequested(async (event) => {

src/pages/Detail/GameInfoEdit.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ function SourceCoverDialog({
214214
disabled={disabled}
215215
className={`block w-30 flex-none overflow-hidden rounded text-left border-2 ${
216216
selected
217-
? "border-[#1976d2] bg-[rgba(25,118,210,0.08)]"
217+
? "border-[--mui-palette-primary-main] bg-[rgba(25,118,210,0.08)]"
218218
: "border-solid border-gray-200 bg-white dark:bg-transparent"
219219
}`}
220220
>

src/pages/Detail/InfoBox.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -235,10 +235,10 @@ export const InfoBox: React.FC<InfoBoxProps> = ({ gameID }: InfoBoxProps) => {
235235
const spanDays =
236236
allDates.length > 1
237237
? Math.ceil(
238-
(new Date(allDates[allDates.length - 1]).getTime() -
239-
new Date(allDates[0]).getTime()) /
240-
(1000 * 60 * 60 * 24),
241-
)
238+
(new Date(allDates[allDates.length - 1]).getTime() -
239+
new Date(allDates[0]).getTime()) /
240+
(1000 * 60 * 60 * 24),
241+
)
242242
: 0;
243243

244244
// 数据点较多 或 时间跨度超过180天 → 按月聚合
@@ -322,7 +322,7 @@ export const InfoBox: React.FC<InfoBoxProps> = ({ gameID }: InfoBoxProps) => {
322322
}
323323
>
324324
<div className="flex items-center space-x-2 mb-2">
325-
<span className="text-[#1976d2] flex-shrink-0 flex items-center">
325+
<span className="text-[--mui-palette-primary-main] flex-shrink-0 flex items-center">
326326
{item.icon}
327327
</span>
328328
<Typography
@@ -429,7 +429,7 @@ export const InfoBox: React.FC<InfoBoxProps> = ({ gameID }: InfoBoxProps) => {
429429
series={[
430430
{
431431
dataKey: "playtime",
432-
color: "#1976d2",
432+
color: "var(--mui-palette-primary-main)",
433433
showMark: chartConfig.showMark,
434434
area: chartConfig.showArea,
435435
},

src/pages/Settings/GeneralSettings.tsx

Lines changed: 122 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
1-
import { FormControlLabel, Radio, RadioGroup, Switch } from "@mui/material";
1+
import {
2+
FormControlLabel,
3+
Radio,
4+
RadioGroup,
5+
Switch,
6+
Tooltip,
7+
} from "@mui/material";
28
import Box from "@mui/material/Box";
39
import InputLabel from "@mui/material/InputLabel";
410
import MenuItem from "@mui/material/MenuItem";
11+
import CheckIcon from "@mui/icons-material/Check";
512
import Select, { type SelectChangeEvent } from "@mui/material/Select";
13+
import { useEffect, useState } from "react";
614
import { useTranslation } from "react-i18next";
715
import { useShallow } from "zustand/react/shallow";
816
import { useStore } from "@/store/appStore";
@@ -141,3 +149,116 @@ export const CardClickModeSettings = () => {
141149
</Box>
142150
);
143151
};
152+
153+
const PRESET_COLORS = [
154+
{ label: "MUI Blue", value: "#1976d2" },
155+
{ label: "Purple", value: "#9c27b0" },
156+
{ label: "Green", value: "#5cd08c" },
157+
{ label: "Red", value: "#d32f2f" },
158+
{ label: "Orange", value: "#ed6c02" },
159+
{ label: "Teal", value: "#009688" },
160+
];
161+
162+
export const ThemeColorSettings = () => {
163+
const { t } = useTranslation();
164+
const themeColor = useStore((state) => state.themeColor);
165+
const setThemeColor = useStore((state) => state.setThemeColor);
166+
const [localColor, setLocalColor] = useState(themeColor);
167+
168+
useEffect(() => {
169+
setLocalColor(themeColor);
170+
}, [themeColor]);
171+
172+
useEffect(() => {
173+
const timer = setTimeout(() => {
174+
if (localColor !== themeColor) {
175+
setThemeColor(localColor);
176+
}
177+
}, 300);
178+
return () => clearTimeout(timer);
179+
}, [localColor, themeColor, setThemeColor]);
180+
181+
const handleColorClick = (color: string) => {
182+
setLocalColor(color);
183+
setThemeColor(color);
184+
};
185+
186+
return (
187+
<Box className="mb-6">
188+
<InputLabel className="font-semibold mb-4">
189+
{t("pages.Settings.themeColor.title", "主题颜色")}
190+
</InputLabel>
191+
<Box className="pl-2 flex flex-wrap gap-4 items-center">
192+
{PRESET_COLORS.map((color) => {
193+
const isSelected =
194+
localColor.toLowerCase() === color.value.toLowerCase();
195+
return (
196+
<Tooltip
197+
key={color.value}
198+
title={t(
199+
`pages.Settings.themeColor.${color.label.toLowerCase().replace(" ", "")}`,
200+
color.label,
201+
)}
202+
>
203+
<Box
204+
onClick={() => handleColorClick(color.value)}
205+
sx={{
206+
width: 36,
207+
height: 36,
208+
borderRadius: "50%",
209+
backgroundColor: color.value,
210+
cursor: "pointer",
211+
display: "flex",
212+
alignItems: "center",
213+
justifyContent: "center",
214+
boxShadow: isSelected ? 3 : 1,
215+
transition: "all 0.2s ease-in-out",
216+
"&:hover": { transform: "scale(1.1)", boxShadow: 4 },
217+
}}
218+
>
219+
{isSelected && (
220+
<CheckIcon sx={{ color: "#fff", fontSize: 20 }} />
221+
)}
222+
</Box>
223+
</Tooltip>
224+
);
225+
})}
226+
227+
<Box className="ml-2 flex items-center gap-3">
228+
<InputLabel className="text-sm m-0">
229+
{t("pages.Settings.themeColor.custom", "自定义:")}
230+
</InputLabel>
231+
<Box
232+
sx={{
233+
width: 36,
234+
height: 36,
235+
borderRadius: "50%",
236+
overflow: "hidden",
237+
cursor: "pointer",
238+
boxShadow: 1,
239+
transition: "all 0.2s ease-in-out",
240+
"&:hover": { transform: "scale(1.1)", boxShadow: 4 },
241+
position: "relative",
242+
}}
243+
>
244+
<input
245+
type="color"
246+
value={localColor}
247+
onChange={(e) => setLocalColor(e.target.value)}
248+
style={{
249+
width: "150%",
250+
height: "150%",
251+
position: "absolute",
252+
top: "-25%",
253+
left: "-25%",
254+
cursor: "pointer",
255+
border: "none",
256+
padding: 0,
257+
}}
258+
/>
259+
</Box>
260+
</Box>
261+
</Box>
262+
</Box>
263+
);
264+
};

src/pages/Settings/SettingsPage.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
CardClickModeSettings,
2626
LanguageSelect,
2727
NsfwSettings,
28+
ThemeColorSettings,
2829
} from "./GeneralSettings";
2930
import { DatabaseBackupSettings } from "./MaintenanceSettings";
3031
import {
@@ -136,6 +137,8 @@ export const Settings: React.FC = () => {
136137
<>
137138
<LanguageSelect />
138139
<Divider className="my-6" />
140+
<ThemeColorSettings />
141+
<Divider className="my-6" />
139142
<NsfwSettings />
140143
<Divider className="my-6" />
141144
<CardClickModeSettings />

src/providers/ToolpadReactRouterAppProvider.tsx

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { createTheme } from "@mui/material/styles";
12
import {
23
AppProvider,
34
type AppProviderProps,
@@ -16,6 +17,7 @@ import {
1617
useNavigate,
1718
useSearchParams,
1819
} from "react-router-dom";
20+
import { useStore } from "@/store/appStore";
1921
import { saveScrollPosition } from "@/utils/scroll";
2022

2123
interface ToolpadLinkProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
@@ -84,7 +86,29 @@ export const ToolpadReactRouterAppProvider = (props: AppProviderProps) => {
8486
[pathname, searchParams, navigateImpl],
8587
);
8688

87-
return <AppProvider router={router} {...props} />;
89+
const themeColor = useStore((state) => state.themeColor);
90+
91+
const theme = useMemo(() => {
92+
return createTheme({
93+
cssVariables: {
94+
colorSchemeSelector: "data-toolpad-color-scheme",
95+
},
96+
colorSchemes: {
97+
light: {
98+
palette: {
99+
primary: { main: themeColor },
100+
},
101+
},
102+
dark: {
103+
palette: {
104+
primary: { main: themeColor },
105+
},
106+
},
107+
},
108+
});
109+
}, [themeColor]);
110+
111+
return <AppProvider router={router} theme={theme} {...props} />;
88112
};
89113

90114
export default ToolpadReactRouterAppProvider;

src/store/appStore.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,10 @@ export interface AppState {
152152
selectedCategory: SelectedCategory; // 当前选中的分类
153153
setCurrentGroup: (groupId: string | null) => void; // 设置当前分组
154154
setSelectedCategory: (category: SelectedCategory) => void; // 设置当前选中的分类
155+
156+
// 主题设置
157+
themeColor: string;
158+
setThemeColor: (color: string) => void;
155159
}
156160

157161
// 创建持久化的全局状态
@@ -399,6 +403,12 @@ export const useStore = create<AppState>()(
399403
set({ selectedCategory: category });
400404
},
401405

406+
// 主题设置默认值
407+
themeColor: "#1976d2",
408+
setThemeColor: (color: string) => {
409+
set({ themeColor: color });
410+
},
411+
402412
// 初始化方法
403413
initialize: async () => {
404414
// 初始化游戏时间跟踪(数据获取由 React Query 自动触发)
@@ -446,6 +456,8 @@ export const useStore = create<AppState>()(
446456
// 分组分类选择状态
447457
currentGroupId: state.currentGroupId,
448458
selectedCategory: state.selectedCategory,
459+
// 主题设置
460+
themeColor: state.themeColor,
449461
}),
450462
version: APP_STORE_VERSION,
451463
migrate: migrateAppStorePersistedState,

src/store/appStoreMigrations.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ type AppStorePersistedState = {
1616
selectedCategoryName?: string | null;
1717
doubleClickLaunch?: boolean;
1818
longPressLaunch?: boolean;
19+
themeColor?: string;
1920
};
2021

2122
type SelectedCategoryState =

0 commit comments

Comments
 (0)