Skip to content
File

Blob: src/client/islands/theme-toggle.tsx

typescript59 lines
1/// <reference lib="dom" />
2 
3import { useEffect, useState } from "react";
4import { Moon, Sun } from "lucide-react";
5 
6import { highlightThemeHref } from "@/client/highlight-theme";
7import { hydrateIsland } from "@/client/hydrate";
8 
9export type ThemeToggleProps = Record<string, never>;
10 
11function applyHighlightTheme(theme: "light" | "dark") {
12 const stylesheet = document.getElementById("hljs-theme");
13 if (!(stylesheet instanceof HTMLLinkElement)) {
14 return;
15 }
16 
17 stylesheet.href = theme === "dark" ? highlightThemeHref.dark : highlightThemeHref.light;
18}
19 
20function applyTheme(theme: "light" | "dark") {
21 document.documentElement.dataset.theme = theme;
22 document.documentElement.classList.toggle("dark", theme === "dark");
23 applyHighlightTheme(theme);
24}
25 
26export function ThemeToggleIsland(_props: ThemeToggleProps) {
27 const [theme, setTheme] = useState<"light" | "dark">("dark");
28 
29 useEffect(() => {
30 const initial = document.documentElement.classList.contains("dark") ? "dark" : "light";
31 setTheme(initial);
32 applyTheme(initial);
33 }, []);
34 
35 return (
36 <button
37 type="button"
38 data-theme-toggle
39 className="flex items-center gap-2 rounded-lg px-2.5 py-1.5 text-zinc-500 transition-colors hover:bg-zinc-100 hover:text-zinc-800 dark:text-zinc-400 dark:hover:bg-zinc-800 dark:hover:text-zinc-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent-500/50 focus-visible:ring-offset-2 focus-visible:ring-offset-canvas"
40 aria-label={`Toggle theme (current: ${theme === "dark" ? "Dark" : "Light"})`}
41 onClick={() => {
42 const nextTheme = theme === "dark" ? "light" : "dark";
43 setTheme(nextTheme);
44 applyTheme(nextTheme);
45 try {
46 localStorage.setItem("theme", nextTheme);
47 } catch {}
48 }}
49 >
50 <Sun className="icon-light h-5 w-5" aria-hidden="true" />
51 <Moon className="icon-dark h-5 w-5" aria-hidden="true" />
52 </button>
53 );
54}
55 
56export function initThemeToggle() {
57 hydrateIsland<ThemeToggleProps>("theme-toggle", ThemeToggleIsland);
58}