mirror of
https://github.com/fankes/komari-theme-purcarte.git
synced 2025-10-18 19:39:22 +08:00
fix: 修复亮暗模式颜色
This commit is contained in:
@@ -2,7 +2,7 @@ import React from "react";
|
||||
|
||||
const Footer: React.FC = () => {
|
||||
return (
|
||||
<footer className="fixed inset-shadow-sm inset-shadow-(color:--accent-4)/50 bottom-0 left-0 right-0 p-2 text-center purcarte-blur z-50">
|
||||
<footer className="fixed inset-shadow-sm inset-shadow-(color:--accent-a4) bottom-0 left-0 right-0 p-2 text-center purcarte-blur z-50">
|
||||
<p className="flex justify-center text-sm text-secondary-foreground theme-text-shadow whitespace-pre">
|
||||
Powered by{" "}
|
||||
<a
|
||||
|
@@ -57,7 +57,7 @@ export const Header = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<header className="purcarte-blur border-b border-(--accent-4)/50 sticky top-0 flex items-center justify-center shadow-sm shadow-(color:--accent-4)/50 z-10">
|
||||
<header className="purcarte-blur border-b border-(--accent-a4) shadow-sm shadow-(color:--accent-a4) sticky top-0 flex items-center justify-center z-10">
|
||||
<div className="w-[90%] max-w-screen-2xl px-4 py-2 flex items-center justify-between">
|
||||
<div className="flex items-center theme-text-shadow text-accent-foreground">
|
||||
<a href="/" className="flex items-center gap-2 text-2xl font-bold">
|
||||
|
@@ -10,14 +10,13 @@ const buttonVariants = cva(
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"bg-(--accent-5)/50 text-primary shadow-sm shadow-(color:--accent-4)/50 hover:bg-(--accent-6)/50",
|
||||
"theme-button text-primary inset-shadow-xs inset-shadow-(color:--accent-a4) ",
|
||||
destructive:
|
||||
"bg-(--accent-5)/50 text-white shadow-sm shadow-(color:--accent-4)/50 hover:bg-(--accent-6)/50 focus-visible:ring-destructive/20",
|
||||
outline:
|
||||
"bg-(--accent-5)/50 theme-card-style hover:bg-(--accent-6)/50 hover:text-accent-foreground",
|
||||
"theme-button text-white inset-shadow-xs inset-shadow-(color:--accent-a4) focus-visible:ring-destructive/20",
|
||||
outline: "theme-button theme-card-style",
|
||||
secondary:
|
||||
"bg-(--accent-5)/50 text-secondary-foreground shadow-sm shadow-(color:--accent-4)/50 hover:bg-(--accent-6)/50",
|
||||
ghost: "hover:bg-(--accent-5)/50 hover:bg-(--accent-6)/50",
|
||||
"theme-button text-secondary-foreground inset-shadow-xs inset-shadow-(color:--accent-a4)",
|
||||
ghost: "theme-button-ghost",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
size: {
|
||||
|
@@ -11,14 +11,14 @@ const Switch = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SwitchPrimitives.Root
|
||||
className={cn(
|
||||
"peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border border-(--accent-5) transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-(--accent-8)/50 data-[state=unchecked]:bg-(--accent-4)/50",
|
||||
"peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border border-(--accent-a5) transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-(--accent-a6) data-[state=unchecked]:bg-(--accent-a2)",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
ref={ref}>
|
||||
<SwitchPrimitives.Thumb
|
||||
className={cn(
|
||||
"pointer-events-none block h-5 w-5 rounded-full bg-(--accent-9) dark:bg-(--accent-5) shadow-sm ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0"
|
||||
"pointer-events-none block h-5 w-5 rounded-full bg-(--accent-a8) shadow-sm ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0"
|
||||
)}
|
||||
/>
|
||||
</SwitchPrimitives.Root>
|
||||
|
11
src/contexts/ThemeContext.tsx
Normal file
11
src/contexts/ThemeContext.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import type { FC, ReactNode } from "react";
|
||||
import { ThemeContext, type ThemeContextType } from "@/hooks/useTheme";
|
||||
|
||||
export const ThemeProvider: FC<{
|
||||
children: ReactNode;
|
||||
value: ThemeContextType;
|
||||
}> = ({ children, value }) => {
|
||||
return (
|
||||
<ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>
|
||||
);
|
||||
};
|
@@ -41,20 +41,66 @@ export const THEME_DEFAULTS = {
|
||||
} as const;
|
||||
|
||||
export interface ThemeContextType {
|
||||
appearance: Appearance;
|
||||
appearance: "light" | "dark";
|
||||
rawAppearance: Appearance;
|
||||
setAppearance: (appearance: Appearance) => void;
|
||||
color: Colors;
|
||||
setColor: (color: Colors) => void;
|
||||
}
|
||||
|
||||
export const ThemeContext = createContext<ThemeContextType>({
|
||||
appearance: THEME_DEFAULTS.appearance,
|
||||
appearance: "light",
|
||||
rawAppearance: THEME_DEFAULTS.appearance,
|
||||
setAppearance: () => {},
|
||||
color: THEME_DEFAULTS.color,
|
||||
setColor: () => {},
|
||||
});
|
||||
|
||||
export const useTheme = () => {
|
||||
/**
|
||||
* Custom hook to convert "system" appearance to actual "light" or "dark" for Radix UI
|
||||
* @param appearance - The appearance setting from context ("light", "dark", or "system")
|
||||
* @returns The resolved appearance for Radix UI ("light" or "dark")
|
||||
*/
|
||||
export const useSystemTheme = (appearance: Appearance): "light" | "dark" => {
|
||||
const [systemTheme, setSystemTheme] = useState<"light" | "dark">(() => {
|
||||
// Initial system theme detection
|
||||
if (typeof window !== "undefined" && window.matchMedia) {
|
||||
return window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||
? "dark"
|
||||
: "light";
|
||||
}
|
||||
return "light";
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === "undefined" || !window.matchMedia) {
|
||||
return;
|
||||
}
|
||||
|
||||
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
||||
|
||||
const handleChange = (e: MediaQueryListEvent) => {
|
||||
setSystemTheme(e.matches ? "dark" : "light");
|
||||
};
|
||||
|
||||
// Add listener for system theme changes
|
||||
mediaQuery.addEventListener("change", handleChange);
|
||||
|
||||
// Cleanup
|
||||
return () => mediaQuery.removeEventListener("change", handleChange);
|
||||
}, []);
|
||||
|
||||
// Return the resolved theme
|
||||
if (appearance === "system") {
|
||||
return systemTheme;
|
||||
}
|
||||
|
||||
return appearance as "light" | "dark";
|
||||
};
|
||||
|
||||
import { useContext } from "react";
|
||||
|
||||
export const useThemeManager = () => {
|
||||
const enableLocalStorage = useConfigItem("enableLocalStorage");
|
||||
const defaultAppearance = useConfigItem("selectedDefaultAppearance");
|
||||
const defaultColor = useConfigItem("selectThemeColor");
|
||||
@@ -86,39 +132,28 @@ export const useTheme = () => {
|
||||
return (defaultColor as Colors) || THEME_DEFAULTS.color;
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (appearance === "system") {
|
||||
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
|
||||
.matches
|
||||
? "dark"
|
||||
: "light";
|
||||
setAppearance(systemTheme);
|
||||
}
|
||||
}, [appearance]);
|
||||
const resolvedAppearance = useSystemTheme(appearance);
|
||||
|
||||
useEffect(() => {
|
||||
const root = window.document.documentElement;
|
||||
root.classList.remove("light", "dark");
|
||||
|
||||
if (appearance === "system") {
|
||||
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
|
||||
.matches
|
||||
? "dark"
|
||||
: "light";
|
||||
root.classList.add(systemTheme);
|
||||
} else {
|
||||
root.classList.add(appearance);
|
||||
}
|
||||
if (enableLocalStorage) {
|
||||
localStorage.setItem("appearance", appearance);
|
||||
}
|
||||
}, [appearance, enableLocalStorage]);
|
||||
|
||||
useEffect(() => {
|
||||
if (enableLocalStorage) {
|
||||
localStorage.setItem("color", color);
|
||||
}
|
||||
}, [color, enableLocalStorage]);
|
||||
}, [appearance, color, enableLocalStorage]);
|
||||
|
||||
return { appearance, setAppearance, color, setColor };
|
||||
return {
|
||||
appearance: resolvedAppearance,
|
||||
rawAppearance: appearance,
|
||||
setAppearance,
|
||||
color,
|
||||
setColor,
|
||||
};
|
||||
};
|
||||
|
||||
export const useTheme = () => {
|
||||
const context = useContext(ThemeContext);
|
||||
if (context === undefined) {
|
||||
throw new Error("useTheme must be used within a ThemeProvider");
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
@@ -78,7 +78,6 @@
|
||||
--sidebar-border: #e5e5e5;
|
||||
--sidebar-ring: #a1a1a1;
|
||||
|
||||
--purcarte-blur: 10px;
|
||||
--body-background-url: url("");
|
||||
}
|
||||
|
||||
@@ -130,20 +129,19 @@
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.dark .radix-themes {
|
||||
--theme-text-muted-color: rgb(from var(--accent-4) r g b / 0.8);
|
||||
--theme-line-muted-color: rgb(from var(--accent-2) r g b / 0.5);
|
||||
}
|
||||
|
||||
.radix-themes {
|
||||
--purcarte-blur: 10px;
|
||||
--purcarte-card-color: var(--card-light);
|
||||
--theme-text-muted-color: rgb(from var(--accent-12) r g b / 0.8);
|
||||
--theme-line-muted-color: rgb(from var(--accent-10) r g b / 0.5);
|
||||
}
|
||||
|
||||
.dark .rt-Badge-tag-transparent {
|
||||
--tag-transparent-bg: rgb(from var(--accent-11) r g b / 0.4);
|
||||
--tag-transparent-color: rgb(from var(--accent-4) r g b / 0.8);
|
||||
@apply bg-(--tag-transparent-bg) text-(--tag-transparent-color);
|
||||
.radix-themes.dark {
|
||||
--purcarte-card-color: var(--card-dark);
|
||||
}
|
||||
|
||||
.purcarte-blur {
|
||||
@apply bg-(--purcarte-card-color)! backdrop-blur-(--purcarte-blur)!;
|
||||
}
|
||||
|
||||
.rt-Badge-tag-transparent {
|
||||
@@ -152,12 +150,20 @@
|
||||
@apply bg-(--tag-transparent-bg) text-(--tag-transparent-color);
|
||||
}
|
||||
|
||||
.theme-button {
|
||||
@apply bg-(--accent-a6)! hover:bg-(--accent-a5)!;
|
||||
}
|
||||
|
||||
.theme-button-ghost {
|
||||
@apply hover:bg-(--accent-a6)! hover:bg-(--accent-a5)!;
|
||||
}
|
||||
|
||||
.theme-text-shadow {
|
||||
@apply text-shadow-sm text-shadow-(color:--accent-8)/50;
|
||||
@apply text-shadow-sm text-shadow-(color:--accent-a4);
|
||||
}
|
||||
|
||||
.theme-card-style {
|
||||
@apply rounded-lg shadow-sm shadow-(color:--accent-4)/50 box-border border border-(--accent-4)/50;
|
||||
@apply rounded-lg shadow-sm shadow-(color:--accent-a4) box-border border border-(--accent-a4);
|
||||
}
|
||||
|
||||
.theme-text-muted {
|
||||
@@ -178,11 +184,6 @@ body::before {
|
||||
/* transition: var(--body-background-transition); */
|
||||
}
|
||||
|
||||
.purcarte-blur {
|
||||
background: var(--color-card);
|
||||
backdrop-filter: blur(var(--purcarte-blur));
|
||||
}
|
||||
|
||||
.striped-bg-red-translucent-diagonal {
|
||||
background-image: linear-gradient(
|
||||
45deg,
|
||||
|
10
src/main.tsx
10
src/main.tsx
@@ -6,7 +6,8 @@ import "@radix-ui/themes/styles.css";
|
||||
import { Theme } from "@radix-ui/themes";
|
||||
import { Header } from "@/components/sections/Header";
|
||||
import { ConfigProvider } from "@/config";
|
||||
import { useTheme } from "@/hooks/useTheme";
|
||||
import { useThemeManager, useTheme } from "@/hooks/useTheme";
|
||||
import { ThemeProvider } from "@/contexts/ThemeContext";
|
||||
import { NodeDataProvider } from "@/contexts/NodeDataContext";
|
||||
import { LiveDataProvider } from "@/contexts/LiveDataContext";
|
||||
import { useNodeData } from "@/contexts/NodeDataContext";
|
||||
@@ -57,7 +58,7 @@ export const AppContent = () => {
|
||||
className="fixed right-0 bottom-0 min-w-full min-h-full w-auto h-auto -z-1 object-cover"></video>
|
||||
)}
|
||||
<Theme
|
||||
appearance={appearance === "system" ? "inherit" : appearance}
|
||||
appearance={appearance}
|
||||
accentColor={color}
|
||||
scaling="110%"
|
||||
style={{ backgroundColor: "transparent" }}>
|
||||
@@ -93,6 +94,7 @@ export const AppContent = () => {
|
||||
|
||||
const App = () => {
|
||||
const { publicSettings, loading } = useNodeData();
|
||||
const themeManager = useThemeManager();
|
||||
|
||||
if (loading) {
|
||||
return <Loading />;
|
||||
@@ -100,7 +102,9 @@ const App = () => {
|
||||
|
||||
return (
|
||||
<ConfigProvider publicSettings={publicSettings}>
|
||||
<AppContent />
|
||||
<ThemeProvider value={themeManager}>
|
||||
<AppContent />
|
||||
</ThemeProvider>
|
||||
</ConfigProvider>
|
||||
);
|
||||
};
|
||||
|
@@ -82,7 +82,7 @@ const PingChart = memo(({ node, hours }: PingChartProps) => {
|
||||
let foundKey = null;
|
||||
// 查找是否可以合并到现有时间点
|
||||
for (const key of timeKeys) {
|
||||
if (Math.abs(key - t) <= 1500) {
|
||||
if (Math.abs(key - t) <= 5000) {
|
||||
foundKey = key;
|
||||
break;
|
||||
}
|
||||
@@ -95,10 +95,11 @@ const PingChart = memo(({ node, hours }: PingChartProps) => {
|
||||
timeKeys.push(useKey);
|
||||
}
|
||||
}
|
||||
grouped[useKey][rec.task_id] = rec.value;
|
||||
grouped[useKey][rec.task_id] = rec.value === -1 ? null : rec.value;
|
||||
}
|
||||
|
||||
let full = Object.values(grouped).sort((a: any, b: any) => a.time - b.time);
|
||||
console.log("Full :", full);
|
||||
|
||||
if (hours !== 0) {
|
||||
const task = pingHistory.tasks;
|
||||
@@ -227,6 +228,8 @@ const PingChart = memo(({ node, hours }: PingChartProps) => {
|
||||
});
|
||||
}, [pingHistory?.records, sortedTasks, timeRange]);
|
||||
|
||||
console.log("chartData:", chartData);
|
||||
|
||||
return (
|
||||
<div className="relative space-y-4">
|
||||
{loading && (
|
||||
@@ -451,8 +454,8 @@ const PingChart = memo(({ node, hours }: PingChartProps) => {
|
||||
{...brushIndices}
|
||||
dataKey="time"
|
||||
height={30}
|
||||
stroke="var(--accent-track)"
|
||||
fill="var(--accent-4)"
|
||||
stroke="var(--theme-text-muted-color)"
|
||||
fill="var(--accent-a4)"
|
||||
alwaysShowText
|
||||
tickFormatter={(time) => {
|
||||
const date = new Date(time);
|
||||
|
Reference in New Issue
Block a user