mirror of
https://github.com/xmanrui/OpenClaw-bot-review.git
synced 2026-07-27 22:25:52 +00:00
feat: add dark/light theme toggle
- Add light theme CSS variables in globals.css - Create ThemeProvider with localStorage persistence (lib/theme.tsx) - Add ThemeSwitcher button (☀️/🌙) to sidebar - Wrap app with ThemeProvider in providers.tsx
This commit is contained in:
@@ -12,6 +12,18 @@
|
||||
--orange: #fb923c;
|
||||
}
|
||||
|
||||
[data-theme="light"] {
|
||||
--bg: #f8fafc;
|
||||
--card: #ffffff;
|
||||
--border: #e2e8f0;
|
||||
--text: #0f172a;
|
||||
--text-muted: #64748b;
|
||||
--accent: #0284c7;
|
||||
--accent2: #7c3aed;
|
||||
--green: #16a34a;
|
||||
--orange: #ea580c;
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
|
||||
+6
-1
@@ -1,8 +1,13 @@
|
||||
"use client";
|
||||
|
||||
import { I18nProvider } from "@/lib/i18n";
|
||||
import { ThemeProvider } from "@/lib/theme";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
export function Providers({ children }: { children: ReactNode }) {
|
||||
return <I18nProvider>{children}</I18nProvider>;
|
||||
return (
|
||||
<ThemeProvider>
|
||||
<I18nProvider>{children}</I18nProvider>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { useI18n, LanguageSwitcher } from "@/lib/i18n";
|
||||
import { ThemeSwitcher } from "@/lib/theme";
|
||||
|
||||
const NAV_ITEMS = [
|
||||
{
|
||||
@@ -65,6 +66,7 @@ export function Sidebar() {
|
||||
</Link>
|
||||
<div className="flex items-center gap-2">
|
||||
<LanguageSwitcher />
|
||||
<ThemeSwitcher />
|
||||
<button
|
||||
onClick={() => setCollapsed(true)}
|
||||
className="text-[var(--text-muted)] hover:text-[var(--text)] transition-colors text-lg"
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
"use client";
|
||||
|
||||
import { createContext, useContext, useState, useCallback, useEffect, ReactNode } from "react";
|
||||
|
||||
export type Theme = "dark" | "light";
|
||||
|
||||
interface ThemeContextType {
|
||||
theme: Theme;
|
||||
setTheme: (t: Theme) => void;
|
||||
toggleTheme: () => void;
|
||||
}
|
||||
|
||||
const ThemeContext = createContext<ThemeContextType>({
|
||||
theme: "dark",
|
||||
setTheme: () => {},
|
||||
toggleTheme: () => {},
|
||||
});
|
||||
|
||||
export function ThemeProvider({ children }: { children: ReactNode }) {
|
||||
const [theme, setThemeState] = useState<Theme>("dark");
|
||||
|
||||
useEffect(() => {
|
||||
const saved = localStorage.getItem("theme") as Theme;
|
||||
if (saved === "light" || saved === "dark") {
|
||||
setThemeState(saved);
|
||||
document.documentElement.setAttribute("data-theme", saved);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const setTheme = useCallback((t: Theme) => {
|
||||
setThemeState(t);
|
||||
document.documentElement.setAttribute("data-theme", t);
|
||||
localStorage.setItem("theme", t);
|
||||
}, []);
|
||||
|
||||
const toggleTheme = useCallback(() => {
|
||||
setTheme(theme === "dark" ? "light" : "dark");
|
||||
}, [theme, setTheme]);
|
||||
|
||||
return (
|
||||
<ThemeContext.Provider value={{ theme, setTheme, toggleTheme }}>
|
||||
{children}
|
||||
</ThemeContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useTheme() {
|
||||
return useContext(ThemeContext);
|
||||
}
|
||||
|
||||
export function ThemeSwitcher() {
|
||||
const { theme, toggleTheme } = useTheme();
|
||||
return (
|
||||
<button
|
||||
onClick={toggleTheme}
|
||||
className="px-2 py-1.5 rounded-lg bg-[var(--bg)] border border-[var(--border)] text-sm hover:border-[var(--accent)] transition cursor-pointer"
|
||||
title={theme === "dark" ? "切换到亮色模式" : "切换到暗色模式"}
|
||||
>
|
||||
{theme === "dark" ? "☀️" : "🌙"}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user