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 = () => {
|
const Footer: React.FC = () => {
|
||||||
return (
|
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">
|
<p className="flex justify-center text-sm text-secondary-foreground theme-text-shadow whitespace-pre">
|
||||||
Powered by{" "}
|
Powered by{" "}
|
||||||
<a
|
<a
|
||||||
|
@@ -57,7 +57,7 @@ export const Header = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
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="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">
|
<div className="flex items-center theme-text-shadow text-accent-foreground">
|
||||||
<a href="/" className="flex items-center gap-2 text-2xl font-bold">
|
<a href="/" className="flex items-center gap-2 text-2xl font-bold">
|
||||||
|
@@ -10,14 +10,13 @@ const buttonVariants = cva(
|
|||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default:
|
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:
|
destructive:
|
||||||
"bg-(--accent-5)/50 text-white shadow-sm shadow-(color:--accent-4)/50 hover:bg-(--accent-6)/50 focus-visible:ring-destructive/20",
|
"theme-button text-white inset-shadow-xs inset-shadow-(color:--accent-a4) focus-visible:ring-destructive/20",
|
||||||
outline:
|
outline: "theme-button theme-card-style",
|
||||||
"bg-(--accent-5)/50 theme-card-style hover:bg-(--accent-6)/50 hover:text-accent-foreground",
|
|
||||||
secondary:
|
secondary:
|
||||||
"bg-(--accent-5)/50 text-secondary-foreground shadow-sm shadow-(color:--accent-4)/50 hover:bg-(--accent-6)/50",
|
"theme-button text-secondary-foreground inset-shadow-xs inset-shadow-(color:--accent-a4)",
|
||||||
ghost: "hover:bg-(--accent-5)/50 hover:bg-(--accent-6)/50",
|
ghost: "theme-button-ghost",
|
||||||
link: "text-primary underline-offset-4 hover:underline",
|
link: "text-primary underline-offset-4 hover:underline",
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
|
@@ -11,14 +11,14 @@ const Switch = React.forwardRef<
|
|||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<SwitchPrimitives.Root
|
<SwitchPrimitives.Root
|
||||||
className={cn(
|
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
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
ref={ref}>
|
ref={ref}>
|
||||||
<SwitchPrimitives.Thumb
|
<SwitchPrimitives.Thumb
|
||||||
className={cn(
|
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>
|
</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;
|
} as const;
|
||||||
|
|
||||||
export interface ThemeContextType {
|
export interface ThemeContextType {
|
||||||
appearance: Appearance;
|
appearance: "light" | "dark";
|
||||||
|
rawAppearance: Appearance;
|
||||||
setAppearance: (appearance: Appearance) => void;
|
setAppearance: (appearance: Appearance) => void;
|
||||||
color: Colors;
|
color: Colors;
|
||||||
setColor: (color: Colors) => void;
|
setColor: (color: Colors) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ThemeContext = createContext<ThemeContextType>({
|
export const ThemeContext = createContext<ThemeContextType>({
|
||||||
appearance: THEME_DEFAULTS.appearance,
|
appearance: "light",
|
||||||
|
rawAppearance: THEME_DEFAULTS.appearance,
|
||||||
setAppearance: () => {},
|
setAppearance: () => {},
|
||||||
color: THEME_DEFAULTS.color,
|
color: THEME_DEFAULTS.color,
|
||||||
setColor: () => {},
|
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 enableLocalStorage = useConfigItem("enableLocalStorage");
|
||||||
const defaultAppearance = useConfigItem("selectedDefaultAppearance");
|
const defaultAppearance = useConfigItem("selectedDefaultAppearance");
|
||||||
const defaultColor = useConfigItem("selectThemeColor");
|
const defaultColor = useConfigItem("selectThemeColor");
|
||||||
@@ -86,39 +132,28 @@ export const useTheme = () => {
|
|||||||
return (defaultColor as Colors) || THEME_DEFAULTS.color;
|
return (defaultColor as Colors) || THEME_DEFAULTS.color;
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
const resolvedAppearance = useSystemTheme(appearance);
|
||||||
if (appearance === "system") {
|
|
||||||
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
|
|
||||||
.matches
|
|
||||||
? "dark"
|
|
||||||
: "light";
|
|
||||||
setAppearance(systemTheme);
|
|
||||||
}
|
|
||||||
}, [appearance]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
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) {
|
if (enableLocalStorage) {
|
||||||
localStorage.setItem("appearance", appearance);
|
localStorage.setItem("appearance", appearance);
|
||||||
}
|
|
||||||
}, [appearance, enableLocalStorage]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (enableLocalStorage) {
|
|
||||||
localStorage.setItem("color", color);
|
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-border: #e5e5e5;
|
||||||
--sidebar-ring: #a1a1a1;
|
--sidebar-ring: #a1a1a1;
|
||||||
|
|
||||||
--purcarte-blur: 10px;
|
|
||||||
--body-background-url: url("");
|
--body-background-url: url("");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,20 +129,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@layer utilities {
|
@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 {
|
.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-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);
|
--theme-line-muted-color: rgb(from var(--accent-10) r g b / 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .rt-Badge-tag-transparent {
|
.radix-themes.dark {
|
||||||
--tag-transparent-bg: rgb(from var(--accent-11) r g b / 0.4);
|
--purcarte-card-color: var(--card-dark);
|
||||||
--tag-transparent-color: rgb(from var(--accent-4) r g b / 0.8);
|
}
|
||||||
@apply bg-(--tag-transparent-bg) text-(--tag-transparent-color);
|
|
||||||
|
.purcarte-blur {
|
||||||
|
@apply bg-(--purcarte-card-color)! backdrop-blur-(--purcarte-blur)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rt-Badge-tag-transparent {
|
.rt-Badge-tag-transparent {
|
||||||
@@ -152,12 +150,20 @@
|
|||||||
@apply bg-(--tag-transparent-bg) text-(--tag-transparent-color);
|
@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 {
|
.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 {
|
.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 {
|
.theme-text-muted {
|
||||||
@@ -178,11 +184,6 @@ body::before {
|
|||||||
/* transition: var(--body-background-transition); */
|
/* transition: var(--body-background-transition); */
|
||||||
}
|
}
|
||||||
|
|
||||||
.purcarte-blur {
|
|
||||||
background: var(--color-card);
|
|
||||||
backdrop-filter: blur(var(--purcarte-blur));
|
|
||||||
}
|
|
||||||
|
|
||||||
.striped-bg-red-translucent-diagonal {
|
.striped-bg-red-translucent-diagonal {
|
||||||
background-image: linear-gradient(
|
background-image: linear-gradient(
|
||||||
45deg,
|
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 { Theme } from "@radix-ui/themes";
|
||||||
import { Header } from "@/components/sections/Header";
|
import { Header } from "@/components/sections/Header";
|
||||||
import { ConfigProvider } from "@/config";
|
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 { NodeDataProvider } from "@/contexts/NodeDataContext";
|
||||||
import { LiveDataProvider } from "@/contexts/LiveDataContext";
|
import { LiveDataProvider } from "@/contexts/LiveDataContext";
|
||||||
import { useNodeData } from "@/contexts/NodeDataContext";
|
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>
|
className="fixed right-0 bottom-0 min-w-full min-h-full w-auto h-auto -z-1 object-cover"></video>
|
||||||
)}
|
)}
|
||||||
<Theme
|
<Theme
|
||||||
appearance={appearance === "system" ? "inherit" : appearance}
|
appearance={appearance}
|
||||||
accentColor={color}
|
accentColor={color}
|
||||||
scaling="110%"
|
scaling="110%"
|
||||||
style={{ backgroundColor: "transparent" }}>
|
style={{ backgroundColor: "transparent" }}>
|
||||||
@@ -93,6 +94,7 @@ export const AppContent = () => {
|
|||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const { publicSettings, loading } = useNodeData();
|
const { publicSettings, loading } = useNodeData();
|
||||||
|
const themeManager = useThemeManager();
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <Loading />;
|
return <Loading />;
|
||||||
@@ -100,7 +102,9 @@ const App = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ConfigProvider publicSettings={publicSettings}>
|
<ConfigProvider publicSettings={publicSettings}>
|
||||||
<AppContent />
|
<ThemeProvider value={themeManager}>
|
||||||
|
<AppContent />
|
||||||
|
</ThemeProvider>
|
||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@@ -82,7 +82,7 @@ const PingChart = memo(({ node, hours }: PingChartProps) => {
|
|||||||
let foundKey = null;
|
let foundKey = null;
|
||||||
// 查找是否可以合并到现有时间点
|
// 查找是否可以合并到现有时间点
|
||||||
for (const key of timeKeys) {
|
for (const key of timeKeys) {
|
||||||
if (Math.abs(key - t) <= 1500) {
|
if (Math.abs(key - t) <= 5000) {
|
||||||
foundKey = key;
|
foundKey = key;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -95,10 +95,11 @@ const PingChart = memo(({ node, hours }: PingChartProps) => {
|
|||||||
timeKeys.push(useKey);
|
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);
|
let full = Object.values(grouped).sort((a: any, b: any) => a.time - b.time);
|
||||||
|
console.log("Full :", full);
|
||||||
|
|
||||||
if (hours !== 0) {
|
if (hours !== 0) {
|
||||||
const task = pingHistory.tasks;
|
const task = pingHistory.tasks;
|
||||||
@@ -227,6 +228,8 @@ const PingChart = memo(({ node, hours }: PingChartProps) => {
|
|||||||
});
|
});
|
||||||
}, [pingHistory?.records, sortedTasks, timeRange]);
|
}, [pingHistory?.records, sortedTasks, timeRange]);
|
||||||
|
|
||||||
|
console.log("chartData:", chartData);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative space-y-4">
|
<div className="relative space-y-4">
|
||||||
{loading && (
|
{loading && (
|
||||||
@@ -451,8 +454,8 @@ const PingChart = memo(({ node, hours }: PingChartProps) => {
|
|||||||
{...brushIndices}
|
{...brushIndices}
|
||||||
dataKey="time"
|
dataKey="time"
|
||||||
height={30}
|
height={30}
|
||||||
stroke="var(--accent-track)"
|
stroke="var(--theme-text-muted-color)"
|
||||||
fill="var(--accent-4)"
|
fill="var(--accent-a4)"
|
||||||
alwaysShowText
|
alwaysShowText
|
||||||
tickFormatter={(time) => {
|
tickFormatter={(time) => {
|
||||||
const date = new Date(time);
|
const date = new Date(time);
|
||||||
|
Reference in New Issue
Block a user