/ opencodehub / src / components / ThemeToggle.tsx
JustAPI Bot fix: resolve ThemeToggle merge conflict markers · 7/14/2026
f589e00
107 lines · 4.0 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
"use client";

import { Monitor, Moon, Sun, Palette } from "lucide-react";
import { useEffect, useState } from "react";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";

type Theme = "light" | "dark" | "system" | "github-dark" | "dracula" | "rose-pine" | "tokyo-night";

function getStoredTheme(): Theme {
  if (typeof window === "undefined") return "dark";
  return (localStorage.getItem("theme") as Theme) || "system";
}

function applyTheme(theme: Theme) {
  const html = document.documentElement;
  const isSystemDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
  const activeTheme: string = theme === "system" ? (isSystemDark ? "dark" : "light") : theme;

  if (activeTheme === "light") {
    html.classList.remove("dark");
    html.removeAttribute("data-theme");
  } else {
    html.classList.add("dark");
    if (activeTheme !== "dark" && activeTheme !== "system") {
      html.setAttribute("data-theme", activeTheme);
    } else {
      html.removeAttribute("data-theme");
    }
  }
}

export function ThemeToggle() {
  const [theme, setTheme] = useState<Theme>("dark");

  useEffect(() => {
    const stored = getStoredTheme();
    setTheme(stored);
    applyTheme(stored);

    const mq = window.matchMedia("(prefers-color-scheme: dark)");
    const handler = () => {
      if (getStoredTheme() === "system") {
        applyTheme("system");
      }
    };
    mq.addEventListener("change", handler);
    return () => mq.removeEventListener("change", handler);
  }, []);

  const changeTheme = (newTheme: Theme) => {
    setTheme(newTheme);
    localStorage.setItem("theme", newTheme);
    applyTheme(newTheme);
  };

  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <button
          className="inline-flex items-center justify-center h-9 w-9 rounded-full border border-border/50 bg-background transition-all hover:bg-accent hover:text-accent-foreground focus:outline-none focus:ring-2 focus:ring-primary/20"
          aria-label="Toggle theme"
        >
          <Palette className="h-4 w-4" />
          <span className="sr-only">Toggle theme</span>
        </button>
      </DropdownMenuTrigger>
      <DropdownMenuContent align="end" className="w-48 bg-popover border-border/50 backdrop-blur-xl">
        <DropdownMenuItem onClick={() => changeTheme("light")} className="cursor-pointer">
          <Sun className="mr-2 h-4 w-4" />
          <span>Light</span>
        </DropdownMenuItem>
        <DropdownMenuItem onClick={() => changeTheme("dark")} className="cursor-pointer">
          <Moon className="mr-2 h-4 w-4" />
          <span>Default Dark</span>
        </DropdownMenuItem>
        <DropdownMenuItem onClick={() => changeTheme("github-dark")} className="cursor-pointer">
          <span className="mr-2 h-4 w-4 rounded-full bg-card border border-border/80"></span>
          <span>GitHub Dark</span>
        </DropdownMenuItem>
        <DropdownMenuItem onClick={() => changeTheme("dracula")} className="cursor-pointer">
          <span className="mr-2 h-4 w-4 rounded-full bg-[#282a36] border border-border/80"></span>
          <span>Dracula</span>
        </DropdownMenuItem>
        <DropdownMenuItem onClick={() => changeTheme("rose-pine")} className="cursor-pointer">
          <span className="mr-2 h-4 w-4 rounded-full bg-[#191724] border border-border/80"></span>
          <span>Rosé Pine</span>
        </DropdownMenuItem>
        <DropdownMenuItem onClick={() => changeTheme("tokyo-night")} className="cursor-pointer">
          <span className="mr-2 h-4 w-4 rounded-full bg-[#1a1b26] border border-border/80"></span>
          <span>Tokyo Night</span>
        </DropdownMenuItem>
        <DropdownMenuItem onClick={() => changeTheme("system")} className="cursor-pointer border-t mt-1 pt-1">
          <Monitor className="mr-2 h-4 w-4" />
          <span>System</span>
        </DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  );
}

export default ThemeToggle;